Merge branch 'development_add_incremental_mode_to_heromodels'

This commit is contained in:
timurgordon
2025-06-25 20:23:20 +03:00
5 changed files with 1219 additions and 3 deletions

View File

@@ -0,0 +1,357 @@
// 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
);
}

View File

@@ -0,0 +1,328 @@
use chrono::{Duration, Utc};
use heromodels::db::{Collection, Db};
use heromodels::models::finance::marketplace::{Bid, Listing, ListingType};
use heromodels::models::finance::asset::AssetType;
use heromodels_core::Model;
// Helper function to print listing details
fn print_listing_details(listing: &Listing) {
println!("\n--- Listing Details ---");
println!("ID: {}", listing.get_id());
println!("Title: {}", listing.title);
println!("Description: {}", listing.description);
println!("Asset ID: {}", listing.asset_id);
println!("Asset Type: {:?}", listing.asset_type);
println!("Seller ID: {}", listing.seller_id);
println!("Price: {} {}", listing.price, listing.currency);
println!("Listing Type: {:?}", listing.listing_type);
println!("Status: {:?}", listing.status);
if let Some(expires_at) = listing.expires_at {
println!("Expires At: {}", expires_at);
} else {
println!("Expires At: Never");
}
if let Some(sold_at) = listing.sold_at {
println!("Sold At: {}", sold_at);
}
if let Some(buyer_id) = &listing.buyer_id {
println!("Buyer ID: {}", buyer_id);
}
if let Some(sale_price) = listing.sale_price {
println!("Sale Price: {} {}", sale_price, listing.currency);
}
println!("Bids: {}", listing.bids.len());
println!("Tags: {:?}", listing.tags);
if let Some(image_url) = &listing.image_url {
println!("Image URL: {}", image_url);
}
println!("Created At: {}", listing.base_data.created_at);
println!("Modified At: {}", listing.base_data.modified_at);
}
// Helper function to print bid details
fn print_bid_details(bid: &Bid) {
println!("\n--- Bid Details ---");
println!("Listing ID: {}", bid.listing_id);
println!("Bidder ID: {}", bid.bidder_id);
println!("Amount: {} {}", bid.amount, bid.currency);
println!("Status: {:?}", bid.status);
println!("Created At: {}", bid.created_at);
}
fn main() {
// Create a new DB instance in /tmp/marketplace_db, and reset before every run
let db = heromodels::db::hero::OurDB::new("/tmp/marketplace_db", true).expect("Can create DB");
println!("Hero Models - Marketplace Example");
println!("================================");
// Create listings with auto-generated IDs
// Fixed price listing
let fixed_price_listing = Listing::new(
None, // Auto-generated ID
"Vintage Guitar",
"A beautiful vintage guitar in excellent condition",
"asset123",
AssetType::Erc721, // NFT representing a physical item
"seller456",
1200.0,
"USD",
ListingType::FixedPrice,
Some(Utc::now() + Duration::days(30)), // Expires in 30 days
vec!["music".to_string(), "instrument".to_string(), "vintage".to_string()],
Some("https://example.com/images/vintage_guitar.jpg"),
);
// Auction listing
let auction_listing = Listing::new(
None, // Auto-generated ID
"Rare Painting",
"A rare painting from the 19th century",
"asset789",
AssetType::Erc721, // NFT representing a physical item
"seller456",
5000.0, // Starting price
"USD",
ListingType::Auction,
Some(Utc::now() + Duration::days(7)), // Auction ends in 7 days
vec!["art".to_string(), "painting".to_string(), "antique".to_string()],
Some("https://example.com/images/rare_painting.jpg"),
);
// Exchange listing
let exchange_listing = Listing::new(
None, // Auto-generated ID
"Digital Artwork NFT",
"A unique digital artwork as an NFT",
"asset101",
AssetType::Erc1155, // Multi-token for digital art
"seller789",
0.5, // Price in ETH
"ETH",
ListingType::Exchange,
Some(Utc::now() + Duration::days(14)), // Expires in 14 days
vec!["digital".to_string(), "nft".to_string(), "art".to_string()],
Some("https://example.com/images/digital_artwork.jpg"),
);
// Save all listings to database and get their assigned IDs and updated models
let (fixed_price_id, db_fixed_price) = db.collection().expect("can open listing collection").set(&fixed_price_listing).expect("can set listing");
let (auction_id, db_auction) = db.collection().expect("can open listing collection").set(&auction_listing).expect("can set listing");
let (exchange_id, db_exchange) = db.collection().expect("can open listing collection").set(&exchange_listing).expect("can set listing");
println!("Fixed Price Listing assigned ID: {}", fixed_price_id);
println!("Auction Listing assigned ID: {}", auction_id);
println!("Exchange Listing assigned ID: {}", exchange_id);
// Print all listings retrieved from database
println!("\n--- Listings Retrieved from Database ---");
println!("\n1. Fixed Price Listing:");
print_listing_details(&db_fixed_price);
println!("\n2. Auction Listing:");
print_listing_details(&db_auction);
println!("\n3. Exchange Listing:");
print_listing_details(&db_exchange);
// Demonstrate working with bids on an auction listing
println!("\n--- Working with Bids ---");
// Create bids for the auction listing
let bid1 = Bid::new(
auction_id,
101, // Bidder ID
5200.0,
"USD",
);
let bid2 = Bid::new(
auction_id,
102, // Bidder ID
5500.0,
"USD",
);
// Print the bids
println!("\n1. First Bid:");
print_bid_details(&bid1);
println!("\n2. Second Bid:");
print_bid_details(&bid2);
// Add bids to the auction listing
let updated_auction = db_auction
.add_bid(bid1.clone())
.expect("can add first bid")
.add_bid(bid2.clone())
.expect("can add second bid");
// Save the updated auction listing
let (_, db_updated_auction) = db.collection()
.expect("can open listing collection")
.set(&updated_auction)
.expect("can set updated auction");
println!("\n3. Auction Listing After Adding Bids:");
print_listing_details(&db_updated_auction);
// Demonstrate retrieving the highest bid
if let Some(highest_bid) = db_updated_auction.highest_bid() {
println!("\n4. Highest Bid:");
print_bid_details(highest_bid);
}
// Demonstrate completing a sale for the fixed price listing
println!("\n--- Completing a Sale ---");
// Complete the fixed price listing sale
let completed_fixed_price = db_fixed_price
.complete_sale("buyer123", 1200.0)
.expect("can complete sale");
// Save the updated listing
let (_, db_completed_fixed_price) = db.collection()
.expect("can open listing collection")
.set(&completed_fixed_price)
.expect("can set completed listing");
println!("\n1. Fixed Price Listing After Sale:");
print_listing_details(&db_completed_fixed_price);
// Demonstrate completing an auction
println!("\n--- Completing an Auction ---");
// Complete the auction with the highest bidder
// Store the bidder_id and amount before moving db_updated_auction
let bidder_id = db_updated_auction.highest_bid().unwrap().bidder_id;
let amount = db_updated_auction.highest_bid().unwrap().amount;
// Now complete the sale
let completed_auction = db_updated_auction
.complete_sale(bidder_id.to_string(), amount)
.expect("can complete auction");
// Save the updated auction listing
let (_, db_completed_auction) = db.collection()
.expect("can open listing collection")
.set(&completed_auction)
.expect("can set completed auction");
println!("\n1. Auction Listing After Completion:");
print_listing_details(&db_completed_auction);
// Demonstrate cancelling a listing
println!("\n--- Cancelling a Listing ---");
// Cancel the exchange listing
let cancelled_exchange = db_exchange
.cancel()
.expect("can cancel listing");
// Save the updated listing
let (_, db_cancelled_exchange) = db.collection()
.expect("can open listing collection")
.set(&cancelled_exchange)
.expect("can set cancelled listing");
println!("\n1. Exchange Listing After Cancellation:");
print_listing_details(&db_cancelled_exchange);
// Demonstrate checking for expired listings
println!("\n--- Checking for Expired Listings ---");
// Create a listing that's already expired
let expired_listing = Listing::new(
None, // Auto-generated ID
"Already Expired Item",
"This item's listing has already expired",
"asset202",
AssetType::Erc721, // NFT representing a physical item
"seller456",
50.0,
"USD",
ListingType::FixedPrice,
Some(Utc::now() - Duration::days(1)), // Expired 1 day ago
vec!["expired".to_string()],
None::<String>,
);
// Save the expired listing
let (expired_id, db_expired) = db.collection()
.expect("can open listing collection")
.set(&expired_listing)
.expect("can set expired listing");
println!("Expired Listing assigned ID: {}", expired_id);
// Check expiration
let checked_expired = db_expired.check_expiration();
// Save the checked listing
let (_, db_checked_expired) = db.collection()
.expect("can open listing collection")
.set(&checked_expired)
.expect("can set checked listing");
println!("\n1. Listing After Expiration Check:");
print_listing_details(&db_checked_expired);
// Demonstrate updating listing details
println!("\n--- Updating Listing Details ---");
// Create a new listing to update
let listing_to_update = Listing::new(
None, // Auto-generated ID
"Original Title",
"Original description",
"asset303",
AssetType::Erc20, // Token for a digital asset
"seller456",
75.0,
"USD",
ListingType::FixedPrice,
Some(Utc::now() + Duration::days(30)),
vec!["original".to_string()],
None::<String>,
);
// Save the listing
let (update_id, db_to_update) = db.collection()
.expect("can open listing collection")
.set(&listing_to_update)
.expect("can set listing to update");
println!("Listing to Update assigned ID: {}", update_id);
println!("\n1. Original Listing:");
print_listing_details(&db_to_update);
// Update the listing details
let updated_listing = db_to_update
.update_details(
Some("Updated Title"),
Some("Updated description with more details"),
Some(85.0),
Some("https://example.com/images/updated_image.jpg"),
)
.add_tags(vec!["updated".to_string(), "premium".to_string()]);
// Save the updated listing
let (_, db_updated_listing) = db.collection()
.expect("can open listing collection")
.set(&updated_listing)
.expect("can set updated listing");
println!("\n2. Listing After Update:");
print_listing_details(&db_updated_listing);
println!("\n--- Model Information ---");
println!("Listing DB Prefix: {}", Listing::db_prefix());
}