use actix_web::{web, HttpResponse, Responder, Result}; use actix_session::Session; use tera::Tera; use serde_json::Value; use serde::{Deserialize, Serialize}; use chrono::{Utc, Duration}; use crate::models::governance::{Proposal, Vote, ProposalStatus, VoteType, VotingResults}; use crate::utils::render_template; /// Controller for handling governance-related routes pub struct GovernanceController; impl GovernanceController { /// Helper function to get user from session fn get_user_from_session(session: &Session) -> Option { session.get::("user").ok().flatten().and_then(|user_json| { serde_json::from_str(&user_json).ok() }) } /// Handles the governance dashboard page route pub async fn index(tmpl: web::Data, session: Session) -> Result { 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); } // Get mock proposals for the dashboard let proposals = Self::get_mock_proposals(); ctx.insert("proposals", &proposals); // Get some statistics let stats = Self::get_mock_statistics(); ctx.insert("stats", &stats); render_template(&tmpl, "governance/index.html", &ctx) } /// Handles the proposal list page route pub async fn proposals(tmpl: web::Data, session: Session) -> Result { let mut ctx = tera::Context::new(); ctx.insert("active_page", "governance"); ctx.insert("active_tab", "proposals"); // Add user to context if available if let Some(user) = Self::get_user_from_session(&session) { ctx.insert("user", &user); } // Get mock proposals let proposals = Self::get_mock_proposals(); ctx.insert("proposals", &proposals); render_template(&tmpl, "governance/proposals.html", &ctx) } /// Handles the proposal detail page route pub async fn proposal_detail( path: web::Path, tmpl: web::Data, session: Session ) -> Result { let proposal_id = path.into_inner(); 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); } // Get mock proposal detail let proposal = Self::get_mock_proposal_by_id(&proposal_id); if let Some(proposal) = proposal { ctx.insert("proposal", &proposal); // Get mock votes for this proposal 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); ctx.insert("results", &results); 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))) } } } } /// Handles the create proposal page route pub async fn create_proposal_form(tmpl: web::Data, session: Session) -> Result { let mut ctx = tera::Context::new(); ctx.insert("active_page", "governance"); ctx.insert("active_tab", "create"); // Add user to context if available if let Some(user) = Self::get_user_from_session(&session) { ctx.insert("user", &user); } else { // Redirect to login if not logged in return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish()); } render_template(&tmpl, "governance/create_proposal.html", &ctx) } /// Handles the submission of a new proposal pub async fn submit_proposal( _form: web::Form, tmpl: web::Data, session: Session ) -> Result { // 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); } // In a real application, we would save the proposal to a database // For now, we'll just redirect to the proposals page with a success message ctx.insert("success", "Proposal created successfully!"); // Get mock proposals let proposals = Self::get_mock_proposals(); ctx.insert("proposals", &proposals); render_template(&tmpl, "governance/proposals.html", &ctx) } /// Handles the submission of a vote on a proposal pub async fn submit_vote( path: web::Path, _form: web::Form, tmpl: web::Data, session: Session ) -> Result { 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); } // 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!"); // Get mock votes for this proposal 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); ctx.insert("results", &results); 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))) } } } } /// Handles the my votes page route pub async fn my_votes(tmpl: web::Data, session: Session) -> Result { let mut ctx = tera::Context::new(); ctx.insert("active_page", "governance"); ctx.insert("active_tab", "my_votes"); // Add user to context if available if let Some(user) = Self::get_user_from_session(&session) { ctx.insert("user", &user); // Get mock votes for this user let votes = Self::get_mock_votes_for_user(1); // Assuming user ID 1 for mock data ctx.insert("votes", &votes); render_template(&tmpl, "governance/my_votes.html", &ctx) } else { // Redirect to login if not logged in Ok(HttpResponse::Found().append_header(("Location", "/login")).finish()) } } // Mock data generation methods /// Generate mock proposals for testing fn get_mock_proposals() -> Vec { let now = Utc::now(); vec![ Proposal { id: "prop-001".to_string(), creator_id: 1, creator_name: "Ibrahim Faraji".to_string(), title: "Establish Zanzibar Digital Trade Hub".to_string(), description: "This proposal aims to create a dedicated digital trade hub within the Zanzibar Digital Freezone to facilitate international e-commerce for local businesses. The hub will provide logistics support, digital marketing services, and regulatory compliance assistance to help Zanzibar businesses reach global markets.".to_string(), status: ProposalStatus::Active, created_at: now - Duration::days(5), updated_at: now - Duration::days(5), voting_starts_at: Some(now - Duration::days(3)), voting_ends_at: Some(now + Duration::days(4)), }, Proposal { id: "prop-002".to_string(), creator_id: 2, creator_name: "Amina Salim".to_string(), title: "ZDFZ Sustainable Tourism Framework".to_string(), description: "A comprehensive framework for sustainable tourism development within the Zanzibar Digital Freezone. This proposal outlines environmental standards, community benefit-sharing mechanisms, and digital infrastructure for eco-tourism businesses. It includes tokenization standards for tourism assets and a certification system for sustainable operators.".to_string(), status: ProposalStatus::Approved, created_at: now - Duration::days(15), updated_at: now - Duration::days(2), voting_starts_at: Some(now - Duration::days(14)), voting_ends_at: Some(now - Duration::days(2)), }, Proposal { id: "prop-003".to_string(), creator_id: 3, creator_name: "Hassan Mwinyi".to_string(), title: "Spice Industry Modernization Initiative".to_string(), description: "This proposal seeks to modernize Zanzibar's traditional spice industry through blockchain-based supply chain tracking, international quality certification, and digital marketplace integration. The initiative will help local spice farmers and processors access premium international markets while preserving traditional cultivation methods.".to_string(), status: ProposalStatus::Draft, created_at: now - Duration::days(1), updated_at: now - Duration::days(1), voting_starts_at: None, voting_ends_at: None, }, Proposal { id: "prop-004".to_string(), creator_id: 1, creator_name: "Ibrahim Faraji".to_string(), title: "ZDFZ Regulatory Framework for Digital Financial Services".to_string(), description: "Establish a comprehensive regulatory framework for digital financial services within the Zanzibar Digital Freezone. This includes licensing requirements for crypto exchanges, digital payment providers, and tokenized asset platforms operating within the zone, while ensuring compliance with international AML/KYC standards.".to_string(), status: ProposalStatus::Rejected, created_at: now - Duration::days(20), updated_at: now - Duration::days(5), voting_starts_at: Some(now - Duration::days(19)), voting_ends_at: Some(now - Duration::days(5)), }, Proposal { id: "prop-005".to_string(), creator_id: 4, creator_name: "Fatma Busaidy".to_string(), title: "Digital Arts Incubator and Artwork Marketplace".to_string(), description: "Create a dedicated digital arts incubator and Artwork marketplace to support Zanzibar's creative economy. The initiative will provide technical training, equipment, and a curated marketplace for local artists to create and sell digital art that celebrates Zanzibar's rich cultural heritage while accessing global markets.".to_string(), status: ProposalStatus::Active, created_at: now - Duration::days(7), updated_at: now - Duration::days(7), voting_starts_at: Some(now - Duration::days(6)), voting_ends_at: Some(now + Duration::days(1)), }, Proposal { id: "prop-006".to_string(), creator_id: 5, creator_name: "Omar Makame".to_string(), title: "Zanzibar Renewable Energy Microgrid Network".to_string(), description: "Develop a network of renewable energy microgrids across the Zanzibar Digital Freezone using tokenized investment and community ownership models. This proposal outlines the technical specifications, governance structure, and token economics for deploying solar and tidal energy systems that will ensure energy independence for the zone.".to_string(), status: ProposalStatus::Active, created_at: now - Duration::days(10), updated_at: now - Duration::days(9), voting_starts_at: Some(now - Duration::days(8)), voting_ends_at: Some(now + Duration::days(6)), }, Proposal { id: "prop-007".to_string(), creator_id: 6, creator_name: "Saida Juma".to_string(), title: "ZDFZ Educational Technology Initiative".to_string(), description: "Establish a comprehensive educational technology program within the Zanzibar Digital Freezone to develop local tech talent. This initiative includes coding academies, blockchain development courses, and digital entrepreneurship training, with a focus on preparing Zanzibar's youth for careers in the zone's growing digital economy.".to_string(), status: ProposalStatus::Draft, created_at: now - Duration::days(3), updated_at: now - Duration::days(2), voting_starts_at: None, voting_ends_at: None, }, ] } /// Get a mock proposal by ID fn get_mock_proposal_by_id(id: &str) -> Option { Self::get_mock_proposals().into_iter().find(|p| p.id == id) } /// Generate mock votes for a specific proposal fn get_mock_votes_for_proposal(proposal_id: &str) -> Vec { let now = Utc::now(); vec![ Vote { id: "vote-001".to_string(), proposal_id: proposal_id.to_string(), voter_id: 1, voter_name: "Robert Callingham".to_string(), vote_type: VoteType::Yes, comment: Some("I strongly support this initiative.".to_string()), created_at: now - Duration::days(2), updated_at: now - Duration::days(2), }, Vote { id: "vote-002".to_string(), proposal_id: proposal_id.to_string(), voter_id: 2, voter_name: "Jane Smith".to_string(), vote_type: VoteType::Yes, comment: None, created_at: now - Duration::days(2), updated_at: now - Duration::days(2), }, Vote { id: "vote-003".to_string(), proposal_id: proposal_id.to_string(), voter_id: 3, voter_name: "Bob Johnson".to_string(), vote_type: VoteType::No, comment: Some("I have concerns about the implementation cost.".to_string()), created_at: now - Duration::days(1), updated_at: now - Duration::days(1), }, Vote { id: "vote-004".to_string(), proposal_id: proposal_id.to_string(), voter_id: 4, voter_name: "Alice Williams".to_string(), vote_type: VoteType::Abstain, comment: Some("I need more information before making a decision.".to_string()), created_at: now - Duration::hours(12), updated_at: now - Duration::hours(12), }, ] } /// Generate mock votes for a specific user fn get_mock_votes_for_user(user_id: i32) -> Vec<(Vote, Proposal)> { let votes = vec![ Vote { id: "vote-001".to_string(), proposal_id: "prop-001".to_string(), voter_id: user_id, voter_name: "Robert Callingham".to_string(), vote_type: VoteType::Yes, comment: Some("I strongly support this initiative.".to_string()), created_at: Utc::now() - Duration::days(2), updated_at: Utc::now() - Duration::days(2), }, Vote { id: "vote-005".to_string(), proposal_id: "prop-002".to_string(), voter_id: user_id, voter_name: "Robert Callingham".to_string(), vote_type: VoteType::No, comment: Some("I don't think this is a priority right now.".to_string()), created_at: Utc::now() - Duration::days(10), updated_at: Utc::now() - Duration::days(10), }, Vote { id: "vote-008".to_string(), proposal_id: "prop-004".to_string(), voter_id: user_id, voter_name: "Robert Callingham".to_string(), vote_type: VoteType::Yes, comment: None, created_at: Utc::now() - Duration::days(18), updated_at: Utc::now() - Duration::days(18), }, Vote { id: "vote-010".to_string(), proposal_id: "prop-005".to_string(), voter_id: user_id, voter_name: "Robert Callingham".to_string(), vote_type: VoteType::Yes, comment: Some("Security is always a top priority.".to_string()), created_at: Utc::now() - Duration::days(5), updated_at: Utc::now() - Duration::days(5), }, ]; let proposals = Self::get_mock_proposals(); votes.into_iter() .filter_map(|vote| { proposals.iter() .find(|p| p.id == vote.proposal_id) .map(|p| (vote.clone(), p.clone())) }) .collect() } /// Generate mock voting results for a proposal fn get_mock_voting_results(proposal_id: &str) -> VotingResults { let votes = Self::get_mock_votes_for_proposal(proposal_id); let mut results = VotingResults::new(proposal_id.to_string()); for vote in votes { results.add_vote(&vote.vote_type); } results } /// Generate mock statistics for the governance dashboard fn get_mock_statistics() -> GovernanceStats { GovernanceStats { total_proposals: 5, active_proposals: 2, approved_proposals: 1, rejected_proposals: 1, draft_proposals: 1, total_votes: 15, participation_rate: 75.0, } } } /// Represents the data submitted in the proposal form #[derive(Debug, Deserialize)] pub struct ProposalForm { /// Title of the proposal pub title: String, /// Description of the proposal pub description: String, /// Start date for voting pub voting_start_date: Option, /// End date for voting pub voting_end_date: Option, } /// Represents the data submitted in the vote form #[derive(Debug, Deserialize)] pub struct VoteForm { /// Type of vote (yes, no, abstain) pub vote_type: String, /// Optional comment explaining the vote pub comment: Option, } /// Represents statistics for the governance dashboard #[derive(Debug, Serialize)] pub struct GovernanceStats { /// Total number of proposals pub total_proposals: usize, /// Number of active proposals pub active_proposals: usize, /// Number of approved proposals pub approved_proposals: usize, /// Number of rejected proposals pub rejected_proposals: usize, /// Number of draft proposals pub draft_proposals: usize, /// Total number of votes cast pub total_votes: usize, /// Participation rate (percentage) pub participation_rate: f64, }