implement governance and flow functionality

This commit is contained in:
Timur Gordon
2025-04-22 02:15:49 +02:00
parent 36812e4178
commit 6ed6737c7e
19 changed files with 3268 additions and 75 deletions

View File

@@ -0,0 +1,478 @@
use actix_web::{web, HttpResponse, Responder, Result};
use actix_session::Session;
use chrono::{Utc, Duration};
use serde::Deserialize;
use tera::Tera;
use std::error::Error; // Add this line
use crate::models::flow::{Flow, FlowStatus, FlowType, FlowStatistics, FlowStep, StepStatus, FlowLog};
use crate::controllers::auth::Claims;
pub struct FlowController;
impl FlowController {
/// Renders the flows dashboard
pub async fn index(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let flows = Self::get_mock_flows();
let stats = FlowStatistics::new(&flows);
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &flows);
ctx.insert("stats", &stats);
ctx.insert("active_flows", &flows.iter().filter(|f| f.status == FlowStatus::InProgress).collect::<Vec<_>>());
ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::<Vec<_>>());
let rendered = tmpl.render("flows/index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Renders the flows list page
pub async fn list_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let flows = Self::get_mock_flows();
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &flows);
let rendered = tmpl.render("flows/flows.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Renders the flow detail page
pub async fn flow_detail(
path: web::Path<String>,
tmpl: web::Data<Tera>,
session: Session
) -> Result<impl Responder> {
let flow_id = path.into_inner();
let user = Self::get_user_from_session(&session);
// Find the flow with the given ID
let flows = Self::get_mock_flows();
let flow = flows.iter().find(|f| f.id == flow_id);
if let Some(flow) = flow {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flow", flow);
let rendered = tmpl.render("flows/flow_detail.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else {
let mut ctx = tera::Context::new();
ctx.insert("error", "Flow not found");
let rendered = tmpl.render("error.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
}
}
/// Renders the create flow page
pub async fn create_flow_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
let rendered = tmpl.render("flows/create_flow.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the create flow form submission
pub async fn create_flow(
_form: web::Form<FlowForm>,
session: Session
) -> impl Responder {
// In a real application, we would create a new flow here
// For now, just redirect to the flows list
HttpResponse::Found()
.append_header(("Location", "/flows"))
.finish()
}
/// Renders the my flows page
pub async fn my_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
let user = Self::get_user_from_session(&session);
if let Some(user) = &user {
let flows = Self::get_mock_flows();
let my_flows = flows.iter()
.filter(|f| f.owner_name == user.sub)
.collect::<Vec<_>>();
let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows");
ctx.insert("user", &user);
ctx.insert("flows", &my_flows);
let rendered = tmpl.render("flows/my_flows.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
eprintln!("Error details: {:?}", e);
eprintln!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else {
Ok(HttpResponse::Found()
.append_header(("Location", "/login"))
.finish())
}
}
/// Handles the advance flow step action
pub async fn advance_flow_step(
path: web::Path<String>,
_session: Session
) -> impl Responder {
let flow_id = path.into_inner();
// In a real application, we would advance the flow step here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Handles the mark flow step as stuck action
pub async fn mark_flow_step_stuck(
path: web::Path<String>,
_form: web::Form<StuckForm>,
_session: Session
) -> impl Responder {
let flow_id = path.into_inner();
// In a real application, we would mark the flow step as stuck here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Handles the add log to flow step action
pub async fn add_log_to_flow_step(
path: web::Path<(String, String)>,
_form: web::Form<LogForm>,
_session: Session
) -> impl Responder {
let (flow_id, _step_id) = path.into_inner();
// In a real application, we would add a log to the flow step here
// For now, just redirect to the flow detail page
HttpResponse::Found()
.append_header(("Location", format!("/flows/{}", flow_id)))
.finish()
}
/// Gets the user from the session
fn get_user_from_session(session: &Session) -> Option<Claims> {
if let Ok(Some(user)) = session.get::<Claims>("user") {
Some(user)
} else {
None
}
}
/// Creates mock flow data for testing
fn get_mock_flows() -> Vec<Flow> {
let mut flows = Vec::new();
// Create a few mock flows
let mut flow1 = Flow {
id: "flow-1".to_string(),
name: "Deploy Website".to_string(),
description: "Deploy a new website to production".to_string(),
flow_type: FlowType::ServiceActivation,
status: FlowStatus::InProgress,
owner_id: "user-1".to_string(),
owner_name: "John Doe".to_string(),
steps: vec![
FlowStep {
id: "step-1-1".to_string(),
name: "Build".to_string(),
description: "Build the website".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(1)),
completed_at: Some(Utc::now() - Duration::hours(23)),
logs: vec![
FlowLog {
id: "log-1-1-1".to_string(),
message: "Build started".to_string(),
timestamp: Utc::now() - Duration::days(1),
},
FlowLog {
id: "log-1-1-2".to_string(),
message: "Build completed successfully".to_string(),
timestamp: Utc::now() - Duration::hours(23),
},
],
},
FlowStep {
id: "step-1-2".to_string(),
name: "Test".to_string(),
description: "Run tests on the website".to_string(),
order: 2,
status: StepStatus::InProgress,
started_at: Some(Utc::now() - Duration::hours(22)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-1-2-1".to_string(),
message: "Tests started".to_string(),
timestamp: Utc::now() - Duration::hours(22),
},
],
},
FlowStep {
id: "step-1-3".to_string(),
name: "Deploy".to_string(),
description: "Deploy the website to production".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(1),
updated_at: Utc::now() - Duration::hours(22),
completed_at: None,
progress_percentage: 33,
current_step: None,
};
// Update the current step
flow1.current_step = flow1.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
let mut flow2 = Flow {
id: "flow-2".to_string(),
name: "Database Migration".to_string(),
description: "Migrate database to new schema".to_string(),
flow_type: FlowType::CompanyRegistration,
status: FlowStatus::Completed,
owner_id: "user-2".to_string(),
owner_name: "Jane Smith".to_string(),
steps: vec![
FlowStep {
id: "step-2-1".to_string(),
name: "Backup".to_string(),
description: "Backup the database".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(2)),
logs: vec![
FlowLog {
id: "log-2-1-1".to_string(),
message: "Backup started".to_string(),
timestamp: Utc::now() - Duration::days(3),
},
FlowLog {
id: "log-2-1-2".to_string(),
message: "Backup completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(2),
},
],
},
FlowStep {
id: "step-2-2".to_string(),
name: "Migrate".to_string(),
description: "Run migration scripts".to_string(),
order: 2,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(3)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(5)),
logs: vec![
FlowLog {
id: "log-2-2-1".to_string(),
message: "Migration started".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(3),
},
FlowLog {
id: "log-2-2-2".to_string(),
message: "Migration completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(5),
},
],
},
FlowStep {
id: "step-2-3".to_string(),
name: "Verify".to_string(),
description: "Verify the migration".to_string(),
order: 3,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(6)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
logs: vec![
FlowLog {
id: "log-2-3-1".to_string(),
message: "Verification started".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(6),
},
FlowLog {
id: "log-2-3-2".to_string(),
message: "Verification completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(7),
},
],
},
],
created_at: Utc::now() - Duration::days(3),
updated_at: Utc::now() - Duration::days(3) + Duration::hours(7),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
progress_percentage: 100,
current_step: None,
};
let mut flow3 = Flow {
id: "flow-3".to_string(),
name: "Server Maintenance".to_string(),
description: "Perform server maintenance".to_string(),
flow_type: FlowType::PaymentProcessing,
status: FlowStatus::Stuck,
owner_id: "user-1".to_string(),
owner_name: "John Doe".to_string(),
steps: vec![
FlowStep {
id: "step-3-1".to_string(),
name: "Backup".to_string(),
description: "Backup the server".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(2)),
completed_at: Some(Utc::now() - Duration::days(2) + Duration::hours(1)),
logs: vec![
FlowLog {
id: "log-3-1-1".to_string(),
message: "Backup started".to_string(),
timestamp: Utc::now() - Duration::days(2),
},
FlowLog {
id: "log-3-1-2".to_string(),
message: "Backup completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(1),
},
],
},
FlowStep {
id: "step-3-2".to_string(),
name: "Update".to_string(),
description: "Update the server".to_string(),
order: 2,
status: StepStatus::Stuck,
started_at: Some(Utc::now() - Duration::days(2) + Duration::hours(2)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-3-2-1".to_string(),
message: "Update started".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(2),
},
FlowLog {
id: "log-3-2-2".to_string(),
message: "Update failed: Disk space issue".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(3),
},
],
},
FlowStep {
id: "step-3-3".to_string(),
name: "Restart".to_string(),
description: "Restart the server".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(2),
updated_at: Utc::now() - Duration::days(2) + Duration::hours(3),
completed_at: None,
progress_percentage: 33,
current_step: None,
};
// Update the current step
flow3.current_step = flow3.steps.iter().find(|s| s.status == StepStatus::Stuck).cloned();
flows.push(flow1);
flows.push(flow2);
flows.push(flow3);
flows
}
}
/// Form for creating a new flow
#[derive(Debug, Deserialize)]
pub struct FlowForm {
/// Flow name
pub name: String,
/// Flow description
pub description: String,
/// Flow type
pub flow_type: String,
}
/// Form for marking a step as stuck
#[derive(Debug, Deserialize)]
pub struct StuckForm {
/// Reason for being stuck
pub reason: String,
}
/// Form for adding a log to a step
#[derive(Debug, Deserialize)]
pub struct LogForm {
/// Log message
pub message: String,
}

View File

@@ -0,0 +1,502 @@
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, ProposalFilter, VotingResults};
/// 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<Value> {
session.get::<String>("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<Tera>, session: Session) -> Result<impl Responder> {
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);
let rendered = tmpl.render("governance/index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the proposal list page route
pub async fn proposals(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
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);
let rendered = tmpl.render("governance/proposals.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the proposal detail page route
pub async fn proposal_detail(
path: web::Path<String>,
tmpl: web::Data<Tera>,
session: Session
) -> Result<impl Responder> {
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);
let rendered = tmpl.render("governance/proposal_detail.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else {
// Proposal not found
ctx.insert("error", "Proposal not found");
let rendered = tmpl.render("error.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
}
}
/// Handles the create proposal page route
pub async fn create_proposal_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
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());
}
let rendered = tmpl.render("governance/create_proposal.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the submission of a new proposal
pub async fn submit_proposal(
form: web::Form<ProposalForm>,
tmpl: web::Data<Tera>,
session: Session
) -> Result<impl Responder> {
// 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);
let rendered = tmpl.render("governance/proposals.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the submission of a vote on a proposal
pub async fn submit_vote(
path: web::Path<String>,
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);
}
// 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);
let rendered = tmpl.render("governance/proposal_detail.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else {
// Proposal not found
ctx.insert("error", "Proposal not found");
let rendered = tmpl.render("error.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
}
}
/// Handles the my votes page route
pub async fn my_votes(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
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);
let rendered = tmpl.render("governance/my_votes.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} 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<Proposal> {
let now = Utc::now();
vec![
Proposal {
id: "prop-001".to_string(),
creator_id: 1,
creator_name: "John Doe".to_string(),
title: "Implement Community Rewards Program".to_string(),
description: "This proposal aims to implement a community rewards program to incentivize active participation in the platform.".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: "Jane Smith".to_string(),
title: "Platform UI Redesign".to_string(),
description: "A comprehensive redesign of the platform's user interface to improve usability and accessibility.".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: "Bob Johnson".to_string(),
title: "Add Support for Mobile Notifications".to_string(),
description: "Implement push notifications for mobile users to improve engagement and user experience.".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: "John Doe".to_string(),
title: "Integrate with Third-party Payment Providers".to_string(),
description: "Add support for additional payment providers to give users more options for transactions.".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: "Alice Williams".to_string(),
title: "Implement Two-Factor Authentication".to_string(),
description: "Enhance security by implementing two-factor authentication for all user accounts.".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)),
},
]
}
/// Get a mock proposal by ID
fn get_mock_proposal_by_id(id: &str) -> Option<Proposal> {
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<Vote> {
let now = Utc::now();
vec![
Vote {
id: "vote-001".to_string(),
proposal_id: proposal_id.to_string(),
voter_id: 1,
voter_name: "John Doe".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: "John Doe".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: "John Doe".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: "John Doe".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: "John Doe".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<String>,
/// End date for voting
pub voting_end_date: Option<String>,
}
/// 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<String>,
}
/// 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,
}

View File

@@ -2,4 +2,14 @@
pub mod home;
pub mod auth;
pub mod ticket;
pub mod calendar;
pub mod calendar;
pub mod governance;
pub mod flow;
// Re-export controllers for easier imports
pub use home::HomeController;
pub use auth::AuthController;
pub use ticket::TicketController;
pub use calendar::CalendarController;
pub use governance::GovernanceController;
pub use flow::FlowController;