From e4e403e2313a311f7e1e7d859f96e6c1af5f054f Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sun, 18 May 2025 09:07:59 +0300 Subject: [PATCH] feat: Integerated the DB: - Added an initialization with the db - Implemented 'add_new_proposal' function to be used in the form --- actix_mvc_app/.cargo/config.toml | 2 + actix_mvc_app/Cargo.lock | 259 +++++++++++++++++++- actix_mvc_app/Cargo.toml | 7 + actix_mvc_app/src/controllers/governance.rs | 194 ++++++++++----- actix_mvc_app/src/db/mod.rs | 1 + actix_mvc_app/src/db/proposals.rs | 44 ++++ actix_mvc_app/src/main.rs | 1 + 7 files changed, 441 insertions(+), 67 deletions(-) create mode 100644 actix_mvc_app/.cargo/config.toml create mode 100644 actix_mvc_app/src/db/mod.rs create mode 100644 actix_mvc_app/src/db/proposals.rs diff --git a/actix_mvc_app/.cargo/config.toml b/actix_mvc_app/.cargo/config.toml new file mode 100644 index 0000000..c91c3f3 --- /dev/null +++ b/actix_mvc_app/.cargo/config.toml @@ -0,0 +1,2 @@ +[net] +git-fetch-with-cli = true diff --git a/actix_mvc_app/Cargo.lock b/actix_mvc_app/Cargo.lock index 169ea39..63aeda6 100644 --- a/actix_mvc_app/Cargo.lock +++ b/actix_mvc_app/Cargo.lock @@ -296,6 +296,8 @@ dependencies = [ "env_logger", "futures", "futures-util", + "heromodels", + "heromodels_core", "jsonwebtoken", "lazy_static", "log", @@ -309,6 +311,14 @@ dependencies = [ "uuid", ] +[[package]] +name = "adapter_macros" +version = "0.1.0" +dependencies = [ + "chrono", + "rhai", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -366,6 +376,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "const-random", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy 0.7.35", @@ -478,6 +490,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.88" @@ -547,6 +565,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "2.9.0" @@ -1285,12 +1323,54 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "heromodels" +version = "0.1.0" +dependencies = [ + "adapter_macros", + "bincode", + "chrono", + "heromodels-derive", + "heromodels_core", + "ourdb", + "rhai", + "rhai_autobind_macros", + "rhai_client_macros", + "rhai_wrapper", + "serde", + "serde_json", + "tst", +] + +[[package]] +name = "heromodels-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "heromodels_core" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -1557,6 +1637,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1756,6 +1845,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +dependencies = [ + "spin", +] + [[package]] name = "nom" version = "7.1.3" @@ -1824,6 +1922,9 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -1841,6 +1942,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "ourdb" +version = "0.1.0" +dependencies = [ + "crc32fast", + "log", + "rand 0.8.5", + "thiserror 1.0.69", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1907,7 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] @@ -2210,6 +2321,75 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rhai" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" +dependencies = [ + "ahash", + "bitflags", + "instant", + "no-std-compat", + "num-traits", + "once_cell", + "rhai_codegen", + "rust_decimal", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_autobind_macros" +version = "0.1.0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rhai_client_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rhai", + "syn", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rhai_macros_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rhai_wrapper" +version = "0.1.0" +dependencies = [ + "chrono", + "rhai", + "rhai_macros_derive", + "serde", +] + [[package]] name = "ring" version = "0.16.20" @@ -2247,6 +2427,16 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rust_decimal" +version = "1.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +dependencies = [ + "arrayvec", + "num-traits", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2421,7 +2611,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.12", "time", ] @@ -2456,6 +2646,17 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + [[package]] name = "socket2" version = "0.4.10" @@ -2488,6 +2689,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -2557,13 +2764,39 @@ dependencies = [ "unic-segment", ] +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2723,6 +2956,14 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tst" +version = "0.1.0" +dependencies = [ + "ourdb", + "thiserror 1.0.69", +] + [[package]] name = "typenum" version = "1.18.0" @@ -2831,6 +3072,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.4" @@ -2888,6 +3135,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/actix_mvc_app/Cargo.toml b/actix_mvc_app/Cargo.toml index d39207f..499c686 100644 --- a/actix_mvc_app/Cargo.toml +++ b/actix_mvc_app/Cargo.toml @@ -15,6 +15,8 @@ env_logger = "0.11.2" log = "0.4.21" dotenv = "0.15.0" chrono = { version = "0.4.35", features = ["serde"] } +heromodels = { path = "../../db/heromodels" } +heromodels_core = { path = "../../db/heromodels_core" } config = "0.14.0" num_cpus = "1.16.0" futures = "0.3.30" @@ -27,3 +29,8 @@ redis = { version = "0.23.0", features = ["tokio-comp"] } jsonwebtoken = "8.3.0" pulldown-cmark = "0.13.0" urlencoding = "2.1.3" + +[patch."https://git.ourworld.tf/herocode/db.git"] +rhai_autobind_macros = { path = "../../rhaj/rhai_autobind_macros" } +rhai_wrapper = { path = "../../rhaj/rhai_wrapper" } + diff --git a/actix_mvc_app/src/controllers/governance.rs b/actix_mvc_app/src/controllers/governance.rs index b485c00..be2b9f4 100644 --- a/actix_mvc_app/src/controllers/governance.rs +++ b/actix_mvc_app/src/controllers/governance.rs @@ -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 { // Try to get user from session first - let session_user = session.get::("user").ok().flatten().and_then(|user_json| { - serde_json::from_str(&user_json).ok() - }); - + let session_user = session + .get::("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, session: Session) -> Result { 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 = proposals.into_iter() + let active_proposals: Vec = 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, - tmpl: web::Data, - session: Session + tmpl: web::Data, + session: Session, ) -> Result { 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, session: Session) -> Result { + pub async fn create_proposal_form( + tmpl: web::Data, + session: Session, + ) -> Result { 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, tmpl: web::Data, - session: Session + session: Session, ) -> Result { 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, _form: web::Form, tmpl: web::Data, - session: Session + session: Session, ) -> Result { 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 { 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 } diff --git a/actix_mvc_app/src/db/mod.rs b/actix_mvc_app/src/db/mod.rs new file mode 100644 index 0000000..8d18108 --- /dev/null +++ b/actix_mvc_app/src/db/mod.rs @@ -0,0 +1 @@ +pub mod proposals; diff --git a/actix_mvc_app/src/db/proposals.rs b/actix_mvc_app/src/db/proposals.rs new file mode 100644 index 0000000..130d332 --- /dev/null +++ b/actix_mvc_app/src/db/proposals.rs @@ -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 { + 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>, + voting_end_date: Option>, +) -> 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::() + .expect("can open proposal collection"); + let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal"); + Ok((proposal_id, saved_proposal)) +} diff --git a/actix_mvc_app/src/main.rs b/actix_mvc_app/src/main.rs index b129b76..a2e58be 100644 --- a/actix_mvc_app/src/main.rs +++ b/actix_mvc_app/src/main.rs @@ -8,6 +8,7 @@ use lazy_static::lazy_static; mod config; mod controllers; +mod db; mod middleware; mod models; mod routes;