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:
Mahmoud Emad
2025-05-18 09:07:59 +03:00
parent 2fd74defab
commit e4e403e231
7 changed files with 441 additions and 67 deletions

View File

@@ -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
}

View File

@@ -0,0 +1 @@
pub mod proposals;

View 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))
}

View File

@@ -8,6 +8,7 @@ use lazy_static::lazy_static;
mod config;
mod controllers;
mod db;
mod middleware;
mod models;
mod routes;