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

@ -0,0 +1,2 @@
[net]
git-fetch-with-cli = true

259
actix_mvc_app/Cargo.lock generated
View File

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

View File

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

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;