feat: Integerated the DB:
- Added an initialization with the db - Implemented 'add_new_proposal' function to be used in the form
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
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::db::proposals;
|
||||
use crate::models::governance::{Proposal, ProposalStatus, Vote, VoteType, VotingResults};
|
||||
use crate::utils::render_template;
|
||||
use actix_session::Session;
|
||||
use actix_web::{HttpResponse, Responder, Result, web};
|
||||
use chrono::{Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tera::Tera;
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
/// Controller for handling governance-related routes
|
||||
pub struct GovernanceController;
|
||||
@@ -15,10 +18,12 @@ impl GovernanceController {
|
||||
/// For testing purposes, this will always return a mock user
|
||||
fn get_user_from_session(session: &Session) -> Option<Value> {
|
||||
// Try to get user from session first
|
||||
let session_user = session.get::<String>("user").ok().flatten().and_then(|user_json| {
|
||||
serde_json::from_str(&user_json).ok()
|
||||
});
|
||||
|
||||
let session_user = session
|
||||
.get::<String>("user")
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|user_json| serde_json::from_str(&user_json).ok());
|
||||
|
||||
// If user is not in session, return a mock user for testing
|
||||
session_user.or_else(|| {
|
||||
// Create a mock user
|
||||
@@ -37,38 +42,39 @@ impl GovernanceController {
|
||||
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 (will always be available with our mock user)
|
||||
let user = Self::get_user_from_session(&session).unwrap();
|
||||
ctx.insert("user", &user);
|
||||
|
||||
|
||||
// Get mock proposals for the dashboard
|
||||
let mut proposals = Self::get_mock_proposals();
|
||||
|
||||
let proposals = Self::get_mock_proposals();
|
||||
|
||||
// Filter for active proposals only
|
||||
let active_proposals: Vec<Proposal> = proposals.into_iter()
|
||||
let active_proposals: Vec<Proposal> = proposals
|
||||
.into_iter()
|
||||
.filter(|p| p.status == ProposalStatus::Active)
|
||||
.collect();
|
||||
|
||||
|
||||
// Sort active proposals by voting end date (ascending)
|
||||
let mut sorted_active_proposals = active_proposals.clone();
|
||||
sorted_active_proposals.sort_by(|a, b| a.voting_ends_at.cmp(&b.voting_ends_at));
|
||||
|
||||
|
||||
ctx.insert("proposals", &sorted_active_proposals);
|
||||
|
||||
|
||||
// Get the nearest deadline proposal for the voting pane
|
||||
if let Some(nearest_proposal) = sorted_active_proposals.first() {
|
||||
ctx.insert("nearest_proposal", nearest_proposal);
|
||||
}
|
||||
|
||||
|
||||
// Get recent activity for the timeline
|
||||
let recent_activity = Self::get_mock_recent_activity();
|
||||
ctx.insert("recent_activity", &recent_activity);
|
||||
|
||||
|
||||
// Get some statistics
|
||||
let stats = Self::get_mock_statistics();
|
||||
ctx.insert("stats", &stats);
|
||||
|
||||
|
||||
render_template(&tmpl, "governance/index.html", &ctx)
|
||||
}
|
||||
|
||||
@@ -77,72 +83,80 @@ impl GovernanceController {
|
||||
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<String>,
|
||||
tmpl: web::Data<Tera>,
|
||||
session: Session
|
||||
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);
|
||||
|
||||
|
||||
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)),
|
||||
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)))
|
||||
Err(actix_web::error::ErrorInternalServerError(format!(
|
||||
"Error: {}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the create proposal page route
|
||||
pub async fn create_proposal_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
|
||||
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 (will always be available with our mock user)
|
||||
let user = Self::get_user_from_session(&session).unwrap();
|
||||
ctx.insert("user", &user);
|
||||
|
||||
|
||||
render_template(&tmpl, "governance/create_proposal.html", &ctx)
|
||||
}
|
||||
|
||||
@@ -150,23 +164,66 @@ impl GovernanceController {
|
||||
pub async fn submit_proposal(
|
||||
_form: web::Form<ProposalForm>,
|
||||
tmpl: web::Data<Tera>,
|
||||
session: Session
|
||||
session: Session,
|
||||
) -> Result<impl Responder> {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("active_page", "governance");
|
||||
|
||||
|
||||
// Add user to context (will always be available with our mock user)
|
||||
let user = Self::get_user_from_session(&session).unwrap();
|
||||
ctx.insert("user", &user);
|
||||
|
||||
// In a real application, we would save the proposal to a database
|
||||
|
||||
let proposal_title = &_form.title;
|
||||
let proposal_description = &_form.description;
|
||||
|
||||
// Use the DB-backed proposal creation
|
||||
// Parse voting_start_date and voting_end_date from the form (YYYY-MM-DD expected)
|
||||
let voting_start_date = _form.voting_start_date.as_ref().and_then(|s| {
|
||||
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
|
||||
.ok()
|
||||
.and_then(|d| d.and_hms_opt(0, 0, 0))
|
||||
.map(|naive| chrono::Utc.from_utc_datetime(&naive))
|
||||
});
|
||||
let voting_end_date = _form.voting_end_date.as_ref().and_then(|s| {
|
||||
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
|
||||
.ok()
|
||||
.and_then(|d| d.and_hms_opt(23, 59, 59))
|
||||
.map(|naive| chrono::Utc.from_utc_datetime(&naive))
|
||||
});
|
||||
|
||||
// Extract user id and name from serde_json::Value
|
||||
let user_id = user
|
||||
.get("id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.unwrap_or(1)
|
||||
.to_string();
|
||||
|
||||
match proposals::create_new_proposal(
|
||||
&user_id,
|
||||
proposal_title,
|
||||
proposal_description,
|
||||
voting_start_date,
|
||||
voting_end_date,
|
||||
) {
|
||||
Ok((proposal_id, saved_proposal)) => {
|
||||
println!(
|
||||
"Proposal saved to DB: ID={}, title={:?}",
|
||||
proposal_id, saved_proposal.title
|
||||
);
|
||||
ctx.insert("success", "Proposal created successfully!");
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Failed to save proposal: {err}");
|
||||
ctx.insert("error", &format!("Failed to save proposal: {err}"));
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -175,47 +232,54 @@ impl GovernanceController {
|
||||
path: web::Path<String>,
|
||||
_form: web::Form<VoteForm>,
|
||||
tmpl: web::Data<Tera>,
|
||||
session: Session
|
||||
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());
|
||||
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)),
|
||||
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)))
|
||||
Err(actix_web::error::ErrorInternalServerError(format!(
|
||||
"Error: {}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,15 +290,15 @@ impl GovernanceController {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("active_page", "governance");
|
||||
ctx.insert("active_tab", "my_votes");
|
||||
|
||||
|
||||
// Add user to context (will always be available with our mock user)
|
||||
let user = Self::get_user_from_session(&session).unwrap();
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -301,7 +365,7 @@ impl GovernanceController {
|
||||
}
|
||||
|
||||
// Mock data generation methods
|
||||
|
||||
|
||||
/// Generate mock proposals for testing
|
||||
fn get_mock_proposals() -> Vec<Proposal> {
|
||||
let now = Utc::now();
|
||||
@@ -489,11 +553,13 @@ impl GovernanceController {
|
||||
updated_at: Utc::now() - Duration::days(5),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
let proposals = Self::get_mock_proposals();
|
||||
votes.into_iter()
|
||||
votes
|
||||
.into_iter()
|
||||
.filter_map(|vote| {
|
||||
proposals.iter()
|
||||
proposals
|
||||
.iter()
|
||||
.find(|p| p.id == vote.proposal_id)
|
||||
.map(|p| (vote.clone(), p.clone()))
|
||||
})
|
||||
@@ -504,11 +570,11 @@ impl GovernanceController {
|
||||
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
|
||||
}
|
||||
|
||||
|
1
actix_mvc_app/src/db/mod.rs
Normal file
1
actix_mvc_app/src/db/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod proposals;
|
44
actix_mvc_app/src/db/proposals.rs
Normal file
44
actix_mvc_app/src/db/proposals.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use chrono::{Duration, Utc};
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::{
|
||||
db::{Collection, Db},
|
||||
models::governance::{Proposal, ProposalStatus},
|
||||
};
|
||||
|
||||
/// The path to the database file. Change this as needed for your environment.
|
||||
pub const DB_PATH: &str = "/tmp/ourdb_governance";
|
||||
|
||||
/// 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> {
|
||||
let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB");
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Creates a new proposal and saves it to the database. Returns the saved proposal and its ID.
|
||||
pub fn create_new_proposal(
|
||||
creator_id: &str,
|
||||
title: &str,
|
||||
description: &str,
|
||||
voting_start_date: Option<chrono::DateTime<Utc>>,
|
||||
voting_end_date: Option<chrono::DateTime<Utc>>,
|
||||
) -> Result<(u32, Proposal), String> {
|
||||
let db = get_db(DB_PATH).expect("Can create DB");
|
||||
|
||||
// Create a new proposal (with auto-generated ID)
|
||||
let mut proposal = Proposal::new(
|
||||
None,
|
||||
creator_id,
|
||||
title,
|
||||
description,
|
||||
voting_start_date.unwrap_or_else(Utc::now),
|
||||
voting_end_date.unwrap_or_else(|| Utc::now() + Duration::days(7)),
|
||||
);
|
||||
proposal.status = ProposalStatus::Draft;
|
||||
|
||||
// Save the proposal to the database
|
||||
let collection = db
|
||||
.collection::<Proposal>()
|
||||
.expect("can open proposal collection");
|
||||
let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal");
|
||||
Ok((proposal_id, saved_proposal))
|
||||
}
|
@@ -8,6 +8,7 @@ use lazy_static::lazy_static;
|
||||
|
||||
mod config;
|
||||
mod controllers;
|
||||
mod db;
|
||||
mod middleware;
|
||||
mod models;
|
||||
mod routes;
|
||||
|
Reference in New Issue
Block a user