feat: Refactor governance models and views

- Moved governance models (`Vote`, `VoteType`, `VotingResults`) from
  `models/governance.rs` to `controllers/governance.rs` for better
  organization and to avoid circular dependencies.  This improves
  maintainability and reduces complexity.
- Updated governance views to use the new model locations.
- Added a limit to the number of recent activities displayed on the
  dashboard for performance optimization.
This commit is contained in:
Mahmoud-Emad 2025-06-03 15:31:50 +03:00
parent 9802d51acc
commit 7e95391a9c
4 changed files with 76 additions and 232 deletions

View File

@ -2,7 +2,7 @@ use crate::db::governance::{
self, create_activity, get_all_activities, get_proposal_by_id, get_proposals, self, create_activity, get_all_activities, get_proposal_by_id, get_proposals,
get_recent_activities, get_recent_activities,
}; };
use crate::models::governance::{Vote, VoteType, VotingResults}; // Note: Now using heromodels directly instead of local governance models
use crate::utils::render_template; use crate::utils::render_template;
use actix_session::Session; use actix_session::Session;
use actix_web::{HttpResponse, Responder, Result, web}; use actix_web::{HttpResponse, Responder, Result, web};
@ -15,6 +15,71 @@ use tera::Tera;
use chrono::prelude::*; use chrono::prelude::*;
/// Simple vote type for UI display
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VoteType {
Yes,
No,
Abstain,
}
/// Simple vote structure for UI display
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vote {
pub id: String,
pub proposal_id: String,
pub voter_id: i32,
pub voter_name: String,
pub vote_type: VoteType,
pub comment: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Vote {
pub fn new(
proposal_id: String,
voter_id: i32,
voter_name: String,
vote_type: VoteType,
comment: Option<String>,
) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
proposal_id,
voter_id,
voter_name,
vote_type,
comment,
created_at: now,
updated_at: now,
}
}
}
/// Simple voting results structure for UI display
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VotingResults {
pub proposal_id: String,
pub yes_count: usize,
pub no_count: usize,
pub abstain_count: usize,
pub total_votes: usize,
}
impl VotingResults {
pub fn new(proposal_id: String) -> Self {
Self {
proposal_id,
yes_count: 0,
no_count: 0,
abstain_count: 0,
total_votes: 0,
}
}
}
/// Controller for handling governance-related routes /// Controller for handling governance-related routes
pub struct GovernanceController; pub struct GovernanceController;
@ -153,9 +218,9 @@ impl GovernanceController {
let stats = Self::calculate_statistics_from_database(&proposals_for_stats); let stats = Self::calculate_statistics_from_database(&proposals_for_stats);
ctx.insert("stats", &stats); ctx.insert("stats", &stats);
// Get recent governance activities from our tracker // Get recent governance activities from our tracker (limit to 4 for dashboard)
let recent_activity = match Self::get_recent_governance_activities() { let recent_activity = match Self::get_recent_governance_activities() {
Ok(activities) => activities, Ok(activities) => activities.into_iter().take(4).collect::<Vec<_>>(),
Err(e) => { Err(e) => {
eprintln!("Failed to load recent activities: {}", e); eprintln!("Failed to load recent activities: {}", e);
Vec::new() Vec::new()

View File

@ -1,222 +0,0 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Represents the status of a governance proposal
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProposalStatus {
/// Proposal is in draft status, not yet open for voting
Draft,
/// Proposal is active and open for voting
Active,
/// Proposal has been approved by the community
Approved,
/// Proposal has been rejected by the community
Rejected,
/// Proposal has been cancelled by the creator
Cancelled,
}
impl std::fmt::Display for ProposalStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProposalStatus::Draft => write!(f, "Draft"),
ProposalStatus::Active => write!(f, "Active"),
ProposalStatus::Approved => write!(f, "Approved"),
ProposalStatus::Rejected => write!(f, "Rejected"),
ProposalStatus::Cancelled => write!(f, "Cancelled"),
}
}
}
/// Represents a vote on a governance proposal
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VoteType {
/// Vote in favor of the proposal
Yes,
/// Vote against the proposal
No,
/// Abstain from voting on the proposal
Abstain,
}
impl std::fmt::Display for VoteType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VoteType::Yes => write!(f, "Yes"),
VoteType::No => write!(f, "No"),
VoteType::Abstain => write!(f, "Abstain"),
}
}
}
/// Represents a governance proposal in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proposal {
/// Unique identifier for the proposal
pub id: String,
/// User ID of the proposal creator
pub creator_id: i32,
/// Name of the proposal creator
pub creator_name: String,
/// Title of the proposal
pub title: String,
/// Detailed description of the proposal
pub description: String,
/// Current status of the proposal
pub status: ProposalStatus,
/// Date and time when the proposal was created
pub created_at: DateTime<Utc>,
/// Date and time when the proposal was last updated
pub updated_at: DateTime<Utc>,
/// Date and time when voting starts
pub voting_starts_at: Option<DateTime<Utc>>,
/// Date and time when voting ends
pub voting_ends_at: Option<DateTime<Utc>>,
}
#[allow(dead_code)]
impl Proposal {
/// Creates a new proposal
pub fn new(creator_id: i32, creator_name: String, title: String, description: String) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
creator_id,
creator_name,
title,
description,
status: ProposalStatus::Draft,
created_at: now,
updated_at: now,
voting_starts_at: None,
voting_ends_at: None,
}
}
/// Updates the proposal status
pub fn update_status(&mut self, status: ProposalStatus) {
self.status = status;
self.updated_at = Utc::now();
}
/// Sets the voting period for the proposal
pub fn set_voting_period(&mut self, starts_at: DateTime<Utc>, ends_at: DateTime<Utc>) {
self.voting_starts_at = Some(starts_at);
self.voting_ends_at = Some(ends_at);
self.updated_at = Utc::now();
}
/// Activates the proposal for voting
pub fn activate(&mut self) {
self.status = ProposalStatus::Active;
self.updated_at = Utc::now();
}
/// Cancels the proposal
pub fn cancel(&mut self) {
self.status = ProposalStatus::Cancelled;
self.updated_at = Utc::now();
}
}
/// Represents a vote cast on a proposal
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vote {
/// Unique identifier for the vote
pub id: String,
/// ID of the proposal being voted on
pub proposal_id: String,
/// User ID of the voter
pub voter_id: i32,
/// Name of the voter
pub voter_name: String,
/// Type of vote cast
pub vote_type: VoteType,
/// Optional comment explaining the vote
pub comment: Option<String>,
/// Date and time when the vote was cast
pub created_at: DateTime<Utc>,
/// Date and time when the vote was last updated
pub updated_at: DateTime<Utc>,
}
#[allow(dead_code)]
impl Vote {
/// Creates a new vote
pub fn new(
proposal_id: String,
voter_id: i32,
voter_name: String,
vote_type: VoteType,
comment: Option<String>,
) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
proposal_id,
voter_id,
voter_name,
vote_type,
comment,
created_at: now,
updated_at: now,
}
}
/// Updates the vote type
pub fn update_vote(&mut self, vote_type: VoteType, comment: Option<String>) {
self.vote_type = vote_type;
self.comment = comment;
self.updated_at = Utc::now();
}
}
/// Represents a filter for searching proposals
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProposalFilter {
/// Filter by proposal status
pub status: Option<String>,
/// Filter by creator ID
pub creator_id: Option<i32>,
/// Search term for title and description
pub search: Option<String>,
}
impl Default for ProposalFilter {
fn default() -> Self {
Self {
status: None,
creator_id: None,
search: None,
}
}
}
/// Represents the voting results for a proposal
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VotingResults {
/// Proposal ID
pub proposal_id: String,
/// Number of yes votes
pub yes_count: usize,
/// Number of no votes
pub no_count: usize,
/// Number of abstain votes
pub abstain_count: usize,
/// Total number of votes
pub total_votes: usize,
}
impl VotingResults {
/// Creates a new VotingResults instance
pub fn new(proposal_id: String) -> Self {
Self {
proposal_id,
yes_count: 0,
no_count: 0,
abstain_count: 0,
total_votes: 0,
}
}
}

View File

@ -4,7 +4,7 @@ pub mod calendar;
pub mod contract; pub mod contract;
pub mod defi; pub mod defi;
pub mod flow; pub mod flow;
pub mod governance;
pub mod marketplace; pub mod marketplace;
pub mod ticket; pub mod ticket;
pub mod user; pub mod user;

View File

@ -87,15 +87,16 @@
<div class="mb-4"> <div class="mb-4">
<h5 class="mb-3">Cast Your Vote</h5> <h5 class="mb-3">Cast Your Vote</h5>
<form> <form action="/governance/proposals/{{ nearest_proposal.base_data.id }}/vote" method="post">
<div class="mb-3"> <div class="mb-3">
<input type="text" class="form-control" placeholder="Optional comment on your vote" <input type="text" class="form-control" name="comment"
aria-label="Vote comment"> placeholder="Optional comment on your vote" aria-label="Vote comment">
</div> </div>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<button type="submit" name="vote" value="yes" class="btn btn-success">Vote Yes</button> <button type="submit" name="vote_type" value="Yes" class="btn btn-success">Vote Yes</button>
<button type="submit" name="vote" value="no" class="btn btn-danger">Vote No</button> <button type="submit" name="vote_type" value="No" class="btn btn-danger">Vote No</button>
<button type="submit" name="vote" value="abstain" class="btn btn-secondary">Abstain</button> <button type="submit" name="vote_type" value="Abstain"
class="btn btn-secondary">Abstain</button>
</div> </div>
</form> </form>
</div> </div>