implement governance and flow functionality
This commit is contained in:
478
actix_mvc_app/src/controllers/flow.rs
Normal file
478
actix_mvc_app/src/controllers/flow.rs
Normal 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,
|
||||
}
|
502
actix_mvc_app/src/controllers/governance.rs
Normal file
502
actix_mvc_app/src/controllers/governance.rs
Normal 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,
|
||||
}
|
@@ -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;
|
Reference in New Issue
Block a user