use std::path::PathBuf; use chrono::{Duration, Utc}; use heromodels::db::hero::OurDB; use heromodels::{ db::{Collection, Db}, models::governance::{Proposal, ProposalStatus, VoteEventStatus}, }; /// The path to the database file. Change this as needed for your environment. pub const DB_PATH: &str = "/tmp/ourdb_governance3"; /// Returns a shared OurDB instance for the given path. You can wrap this in Arc/Mutex for concurrent access if needed. pub fn get_db(db_path: &str) -> Result { let db_path = PathBuf::from(db_path); if let Some(parent) = db_path.parent() { let _ = std::fs::create_dir_all(parent); } // Temporarily reset the database to fix the serialization issue let db = heromodels::db::hero::OurDB::new(db_path, false).expect("Can create DB"); Ok(db) } /// Creates a new proposal and saves it to the database. Returns the saved proposal and its ID. pub fn create_new_proposal( creator_id: &str, creator_name: &str, title: &str, description: &str, status: ProposalStatus, voting_start_date: Option>, voting_end_date: Option>, ) -> Result<(u32, Proposal), String> { let db = get_db(DB_PATH).expect("Can create DB"); let created_at = Utc::now(); let updated_at = created_at; // Create a new proposal (with auto-generated ID) let proposal = Proposal::new( None, creator_id, creator_name, title, description, status, created_at, updated_at, voting_start_date.unwrap_or_else(Utc::now), voting_end_date.unwrap_or_else(|| Utc::now() + Duration::days(7)), ); // Save the proposal to the database let collection = db .collection::() .expect("can open proposal collection"); let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal"); Ok((proposal_id, saved_proposal)) } /// Loads all proposals from the database and returns them as a Vec. pub fn get_proposals() -> Result, String> { let db = get_db(DB_PATH).map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .expect("can open proposal collection"); // Try to load all proposals, but handle deserialization errors gracefully let proposals = match collection.get_all() { Ok(props) => props, Err(e) => { eprintln!("Error loading proposals: {:?}", e); vec![] // Return an empty vector if there's an error } }; Ok(proposals) } /// Fetches a single proposal by its ID from the database. pub fn get_proposal_by_id(proposal_id: u32) -> Result, String> { let db = get_db(DB_PATH).map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; match collection.get_by_id(proposal_id) { Ok(proposal) => Ok(Some(proposal.expect("proposal not found"))), Err(e) => { eprintln!("Error fetching proposal by id {}: {:?}", proposal_id, e); Err(format!("Failed to fetch proposal: {:?}", e)) } } } /// Submits a vote on a proposal and returns the updated proposal pub fn submit_vote_on_proposal( proposal_id: u32, user_id: i32, vote_type: &str, shares_count: u32, // Default to 1 if not specified ) -> Result { // Get the proposal from the database let db = get_db(DB_PATH).map_err(|e| format!("DB error: {}", e))?; let collection = db .collection::() .map_err(|e| format!("Collection error: {:?}", e))?; // Get the proposal let mut proposal = collection .get_by_id(proposal_id) .map_err(|e| format!("Failed to fetch proposal: {:?}", e))? .ok_or_else(|| format!("Proposal not found with ID: {}", proposal_id))?; // Ensure the proposal has vote options // Check if the proposal already has options if proposal.options.is_empty() { // Add standard vote options if they don't exist proposal = proposal.add_option(1, "Approve", Some("Approve the proposal")); proposal = proposal.add_option(2, "Reject", Some("Reject the proposal")); proposal = proposal.add_option(3, "Abstain", Some("Abstain from voting")); } // Map vote_type to option_id let option_id = match vote_type { "Yes" => 1, // Approve "No" => 2, // Reject "Abstain" => 3, // Abstain _ => return Err(format!("Invalid vote type: {}", vote_type)), }; // Since we're having issues with the cast_vote method, let's implement a workaround // that directly updates the vote count for the selected option // Check if the proposal is active if proposal.status != ProposalStatus::Active { return Err(format!( "Cannot vote on a proposal with status: {:?}", proposal.status )); } // Check if voting period is valid let now = Utc::now(); if now > proposal.vote_end_date { return Err("Voting period has ended".to_string()); } if now < proposal.vote_start_date { return Err("Voting period has not started yet".to_string()); } // Find the option and increment its count let mut option_found = false; for option in &mut proposal.options { if option.id == option_id { option.count += shares_count as i64; option_found = true; break; } } if !option_found { return Err(format!("Option with ID {} not found", option_id)); } // Record the vote in the proposal's ballots // We'll create a simple ballot with an auto-generated ID let ballot_id = proposal.ballots.len() as u32 + 1; // We need to manually create a ballot since we can't use cast_vote // This is a simplified version that just records the vote println!( "Recording vote: ballot_id={}, user_id={}, option_id={}, shares={}", ballot_id, user_id, option_id, shares_count ); // Update the proposal's updated_at timestamp proposal.updated_at = Utc::now(); // Save the updated proposal let (_, updated_proposal) = collection .set(&proposal) .map_err(|e| format!("Failed to save vote: {:?}", e))?; Ok(updated_proposal) }