WIP: development_backend #4
@@ -134,8 +134,8 @@ impl GovernanceController {
 | 
			
		||||
            let votes = Self::get_mock_votes_for_proposal(&proposal_id);
 | 
			
		||||
            ctx.insert("votes", &votes);
 | 
			
		||||
 | 
			
		||||
            // Get voting results
 | 
			
		||||
            let results = Self::get_mock_voting_results(&proposal_id);
 | 
			
		||||
            // Calculate voting results directly from the proposal
 | 
			
		||||
            let results = Self::calculate_voting_results_from_proposal(&proposal);
 | 
			
		||||
            ctx.insert("results", &results);
 | 
			
		||||
 | 
			
		||||
            render_template(&tmpl, "governance/proposal_detail.html", &ctx)
 | 
			
		||||
@@ -264,57 +264,62 @@ impl GovernanceController {
 | 
			
		||||
    /// Handles the submission of a vote on a proposal
 | 
			
		||||
    pub async fn submit_vote(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        _form: web::Form<VoteForm>,
 | 
			
		||||
        form: web::Form<VoteForm>,
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        session: Session,
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let proposal_id = path.into_inner();
 | 
			
		||||
 | 
			
		||||
        // Check if user is logged in
 | 
			
		||||
        if Self::get_user_from_session(&session).is_none() {
 | 
			
		||||
            return Ok(HttpResponse::Found()
 | 
			
		||||
                .append_header(("Location", "/login"))
 | 
			
		||||
                .finish());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        // Check if user is logged in
 | 
			
		||||
        let user = match Self::get_user_from_session(&session) {
 | 
			
		||||
            Some(user) => user,
 | 
			
		||||
            None => {
 | 
			
		||||
                return Ok(HttpResponse::Found()
 | 
			
		||||
                    .append_header(("Location", "/login"))
 | 
			
		||||
                    .finish());
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        ctx.insert("user", &user);
 | 
			
		||||
 | 
			
		||||
        // Get mock proposal detail
 | 
			
		||||
        let proposal = Self::get_mock_proposal_by_id(&proposal_id);
 | 
			
		||||
        if let Some(proposal) = proposal {
 | 
			
		||||
            ctx.insert("proposal", &proposal);
 | 
			
		||||
            ctx.insert("success", "Your vote has been recorded!");
 | 
			
		||||
        // Extract user ID
 | 
			
		||||
        let user_id = user.get("id").and_then(|v| v.as_i64()).unwrap_or(1) as i32;
 | 
			
		||||
 | 
			
		||||
            // Get mock votes for this proposal
 | 
			
		||||
            let votes = Self::get_mock_votes_for_proposal(&proposal_id);
 | 
			
		||||
            ctx.insert("votes", &votes);
 | 
			
		||||
        // Parse proposal ID
 | 
			
		||||
        let proposal_id_u32 = match proposal_id.parse::<u32>() {
 | 
			
		||||
            Ok(id) => id,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                ctx.insert("error", "Invalid proposal ID");
 | 
			
		||||
                return render_template(&tmpl, "error.html", &ctx);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            // Get voting results
 | 
			
		||||
            let results = Self::get_mock_voting_results(&proposal_id);
 | 
			
		||||
            ctx.insert("results", &results);
 | 
			
		||||
        // Submit the vote
 | 
			
		||||
        match crate::db::proposals::submit_vote_on_proposal(
 | 
			
		||||
            proposal_id_u32,
 | 
			
		||||
            user_id,
 | 
			
		||||
            &form.vote_type,
 | 
			
		||||
            1, // Default to 1 share
 | 
			
		||||
        ) {
 | 
			
		||||
            Ok(updated_proposal) => {
 | 
			
		||||
                ctx.insert("proposal", &updated_proposal);
 | 
			
		||||
                ctx.insert("success", "Your vote has been recorded!");
 | 
			
		||||
 | 
			
		||||
            render_template(&tmpl, "governance/proposal_detail.html", &ctx)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Proposal not found
 | 
			
		||||
            ctx.insert("error", "Proposal not found");
 | 
			
		||||
            // For the error page, we'll use a special case to set the status code to 404
 | 
			
		||||
            match tmpl.render("error.html", &ctx) {
 | 
			
		||||
                Ok(content) => Ok(HttpResponse::NotFound()
 | 
			
		||||
                    .content_type("text/html")
 | 
			
		||||
                    .body(content)),
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    eprintln!("Error rendering error template: {}", e);
 | 
			
		||||
                    Err(actix_web::error::ErrorInternalServerError(format!(
 | 
			
		||||
                        "Error: {}",
 | 
			
		||||
                        e
 | 
			
		||||
                    )))
 | 
			
		||||
                }
 | 
			
		||||
                // Get votes for this proposal
 | 
			
		||||
                // For now, we'll still use mock votes until we implement a function to extract votes from the proposal
 | 
			
		||||
                let votes = Self::get_mock_votes_for_proposal(&proposal_id);
 | 
			
		||||
                ctx.insert("votes", &votes);
 | 
			
		||||
 | 
			
		||||
                // Calculate voting results directly from the updated proposal
 | 
			
		||||
                let results = Self::calculate_voting_results_from_proposal(&updated_proposal);
 | 
			
		||||
                ctx.insert("results", &results);
 | 
			
		||||
 | 
			
		||||
                render_template(&tmpl, "governance/proposal_detail.html", &ctx)
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                ctx.insert("error", &format!("Failed to submit vote: {}", e));
 | 
			
		||||
                render_template(&tmpl, "error.html", &ctx)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -614,6 +619,26 @@ impl GovernanceController {
 | 
			
		||||
        results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate voting results from a proposal
 | 
			
		||||
    fn calculate_voting_results_from_proposal(proposal: &Proposal) -> VotingResults {
 | 
			
		||||
        let mut results = VotingResults::new(proposal.base_data.id.to_string());
 | 
			
		||||
 | 
			
		||||
        // Count votes for each option
 | 
			
		||||
        for option in &proposal.options {
 | 
			
		||||
            match option.id {
 | 
			
		||||
                1 => results.yes_count = option.count as usize,
 | 
			
		||||
                2 => results.no_count = option.count as usize,
 | 
			
		||||
                3 => results.abstain_count = option.count as usize,
 | 
			
		||||
                _ => {} // Ignore other options
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Calculate total votes
 | 
			
		||||
        results.total_votes = results.yes_count + results.no_count + results.abstain_count;
 | 
			
		||||
 | 
			
		||||
        results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate mock statistics for the governance dashboard
 | 
			
		||||
    fn get_mock_statistics() -> GovernanceStats {
 | 
			
		||||
        GovernanceStats {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,11 @@ use chrono::{Duration, Utc};
 | 
			
		||||
use heromodels::db::hero::OurDB;
 | 
			
		||||
use heromodels::{
 | 
			
		||||
    db::{Collection, Db},
 | 
			
		||||
    models::governance::{Proposal, ProposalStatus},
 | 
			
		||||
    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_governance2";
 | 
			
		||||
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<OurDB, String> {
 | 
			
		||||
@@ -90,3 +90,96 @@ pub fn get_proposal_by_id(proposal_id: u32) -> Result<Option<Proposal>, String>
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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<Proposal, String> {
 | 
			
		||||
    // Get the proposal from the database
 | 
			
		||||
    let db = get_db(DB_PATH).map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Proposal>()
 | 
			
		||||
        .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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -147,6 +147,16 @@
 | 
			
		||||
                    <a href="/login" class="btn btn-primary">Login to Vote</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% elif proposal.status != "Active" %}
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="alert alert-warning mb-0">
 | 
			
		||||
                        <i class="bi bi-exclamation-triangle-fill me-2"></i>
 | 
			
		||||
                        <strong>Note:</strong> Voting is only available for proposals with an Active status.
 | 
			
		||||
                        This proposal's current status is <strong>{{ proposal.status }}</strong>.
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user