feat: Add get_all method to list all db objects based on the object model

This commit is contained in:
Mahmoud-Emad 2025-05-21 09:13:58 +03:00
parent bd36d6bda0
commit 4c0c7be574
7 changed files with 225 additions and 87 deletions

View File

@ -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::<Proposal>().expect("can open proposal collection");
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 - 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());
}

View File

@ -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<PathBuf>, reset: bool, id_sequence: IdSequence) -> Result<Self, tst::Error> {
pub fn with_id_sequence(
path: impl Into<PathBuf>,
reset: bool,
id_sequence: IdSequence,
) -> Result<Self, tst::Error> {
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<Box<dyn super::Transaction<Error = Self::Error>>, super::Error<Self::Error>> {
fn begin_transaction(
&self,
) -> Result<Box<dyn super::Transaction<Error = Self::Error>>, super::Error<Self::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::<M>(&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::<M>(&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<Vec<M>, super::Error<Self::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::<M>(&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(())
}

View File

@ -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<Utc>, end_time: DateTime<Utc>) -> Self {
pub fn new(
id: String,
title: impl ToString,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
) -> 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<Utc>, new_end_time: DateTime<Utc>) -> Self {
pub fn reschedule(
mut self,
new_start_time: DateTime<Utc>,
new_end_time: DateTime<Utc>,
) -> Self {
// Basic validation: end_time should be after start_time
if new_end_time > new_start_time {
self.start_time = new_start_time;

View File

@ -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 {

View File

@ -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 {

View File

@ -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<Utc>, // 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<Self, &'static str> {
pub fn complete_sale(
mut self,
buyer_id: impl ToString,
sale_price: f64,
) -> Result<Self, &'static str> {
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;

View File

@ -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<i64>, // 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<crate::db::hero::OurDB>")]
@ -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<u32>, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime<Utc>, vote_end_date: DateTime<Utc>) -> Self {
pub fn new(
id: Option<u32>,
creator_id: impl ToString,
title: impl ToString,
description: impl ToString,
vote_start_date: DateTime<Utc>,
vote_end_date: DateTime<Utc>,
) -> 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<u32>, user_id: u32, chosen_option_id: u8, shares: i64) -> Self {
pub fn cast_vote(
mut self,
ballot_id: Option<u32>,
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