This commit is contained in:
despiegk 2025-04-20 08:56:34 +02:00
parent 25983f701a
commit 4b2e8ca6b9
16 changed files with 343 additions and 112 deletions

21
acldb/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "acldb"
version = "0.1.0"
edition = "2021"
description = "HeroDB ACL Layer: Implements ACL logic, data ops, and Actix RPC server as specified in instructions.md."
[dependencies]
ourdb = { path = "../ourdb" }
tst = { path = "../tst" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
actix-web = "4"
actix-rt = "2"
actix-cors = "0.6"
openapi = "0.6"
tokio = { version = "1", features = ["full"] }
log = "0.4"
thiserror = "1.0"
sha2 = "0.10"
hex = "0.4"

View File

@ -1,12 +1,14 @@
use chrono::{Utc, Duration}; use chrono::{Utc, Duration};
use herodb::db::{DBBuilder, SledDB, SledModel}; use herodb::db::{DBBuilder, DB};
use herodb::models::gov::{ use herodb::models::gov::{
Company, CompanyStatus, BusinessType, Company, CompanyStatus, BusinessType,
Shareholder, ShareholderType, Shareholder, ShareholderType,
Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus, Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus,
User, User,
Vote, VoteOption, Ballot, VoteStatus, Vote, VoteOption, Ballot, VoteStatus,
Resolution, ResolutionStatus, Approval Resolution, ResolutionStatus, Approval,
Committee, CommitteeRole, CommitteeMember,
ComplianceRequirement, ComplianceDocument, ComplianceAudit
}; };
use std::path::PathBuf; use std::path::PathBuf;
use std::fs; use std::fs;
@ -31,6 +33,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.register_model::<User>() .register_model::<User>()
.register_model::<Vote>() .register_model::<Vote>()
.register_model::<Resolution>() .register_model::<Resolution>()
.register_model::<Committee>()
.register_model::<ComplianceRequirement>()
.register_model::<ComplianceDocument>()
.register_model::<ComplianceAudit>()
.build()?; .build()?;
println!("\n1. Creating a Company"); println!("\n1. Creating a Company");
@ -325,7 +331,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("---------------------------"); println!("---------------------------");
// Retrieve company and related objects // Retrieve company and related objects
let retrieved_company = db.get::<Company>(&company.id.to_string())?; let retrieved_company = db.get::<Company>(company.id)?;
println!("Company: {} (ID: {})", retrieved_company.name, retrieved_company.id); println!("Company: {} (ID: {})", retrieved_company.name, retrieved_company.id);
// Get resolutions for this company // Get resolutions for this company
@ -336,7 +342,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
// Get meeting and its resolutions // Get meeting and its resolutions
let retrieved_meeting = db.get::<Meeting>(&meeting.id.to_string())?; let retrieved_meeting = db.get::<Meeting>(meeting.id)?;
println!("Meeting: {} ({})", retrieved_meeting.title, retrieved_meeting.date.format("%Y-%m-%d")); println!("Meeting: {} ({})", retrieved_meeting.title, retrieved_meeting.date.format("%Y-%m-%d"));
let meeting_resolutions = retrieved_meeting.get_resolutions(&db)?; let meeting_resolutions = retrieved_meeting.get_resolutions(&db)?;
@ -346,7 +352,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
// Get vote and its resolution // Get vote and its resolution
let retrieved_vote = db.get::<Vote>(&vote.id.to_string())?; let retrieved_vote = db.get::<Vote>(vote.id)?;
println!("Vote: {} (Status: {:?})", retrieved_vote.title, retrieved_vote.status); println!("Vote: {} (Status: {:?})", retrieved_vote.title, retrieved_vote.status);
if let Ok(Some(vote_res)) = retrieved_vote.get_resolution(&db) { if let Ok(Some(vote_res)) = retrieved_vote.get_resolution(&db) {
@ -354,7 +360,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
// Get resolution and its related objects // Get resolution and its related objects
let retrieved_resolution = db.get::<Resolution>(&resolution.id.to_string())?; let retrieved_resolution = db.get::<Resolution>(resolution.id)?;
println!("Resolution: {} (Status: {:?})", retrieved_resolution.title, retrieved_resolution.status); println!("Resolution: {} (Status: {:?})", retrieved_resolution.title, retrieved_resolution.status);
if let Ok(Some(res_meeting)) = retrieved_resolution.get_meeting(&db) { if let Ok(Some(res_meeting)) = retrieved_resolution.get_meeting(&db) {

View File

@ -26,5 +26,5 @@ pub trait Model: Storable + Debug + Clone + Send + Sync + 'static {
fn db_prefix() -> &'static str; fn db_prefix() -> &'static str;
} }
// Implement Storable for common types that might be used in models // Note: We don't provide a blanket implementation of Storable
impl<T: Serialize + for<'de> Deserialize<'de> + Sized> Storable for T {} // Each model type must implement Storable explicitly

View File

@ -2,6 +2,10 @@ use crate::db::db::DB;
use crate::db::model::Model; use crate::db::model::Model;
use crate::impl_model_methods; use crate::impl_model_methods;
use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice}; use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice};
use crate::models::gov::{
Company, Shareholder, Meeting, User, Vote, Resolution,
Committee, ComplianceRequirement, ComplianceDocument, ComplianceAudit
};
// Implement model-specific methods for Product // Implement model-specific methods for Product
impl_model_methods!(Product, product, products); impl_model_methods!(Product, product, products);
@ -26,3 +30,33 @@ impl_model_methods!(Contract, contract, contracts);
// Implement model-specific methods for Invoice // Implement model-specific methods for Invoice
impl_model_methods!(Invoice, invoice, invoices); impl_model_methods!(Invoice, invoice, invoices);
// Implement model-specific methods for Company
impl_model_methods!(Company, company, companies);
// Implement model-specific methods for Shareholder
impl_model_methods!(Shareholder, shareholder, shareholders);
// Implement model-specific methods for Meeting
impl_model_methods!(Meeting, meeting, meetings);
// Implement model-specific methods for User
impl_model_methods!(User, user, users);
// Implement model-specific methods for Vote
impl_model_methods!(Vote, vote, votes);
// Implement model-specific methods for Resolution
impl_model_methods!(Resolution, resolution, resolutions);
// Implement model-specific methods for Committee
impl_model_methods!(Committee, committee, committees);
// Implement model-specific methods for ComplianceRequirement
impl_model_methods!(ComplianceRequirement, compliance_requirement, compliance_requirements);
// Implement model-specific methods for ComplianceDocument
impl_model_methods!(ComplianceDocument, compliance_document, compliance_documents);
// Implement model-specific methods for ComplianceAudit
impl_model_methods!(ComplianceAudit, compliance_audit, compliance_audits);

143
herodb/src/instructions.md Normal file
View File

@ -0,0 +1,143 @@
# HeroDB: ACL Layer Implementation
## Project Overview
Create a new module that implements an Access Control List (ACL) layer on top of the existing `ourdb` and `tst` databases. This module will manage permissions and access control for data stored in the database system.
call this module: acldb
implement in acldb
remark: there is no dependency on herodb
## Architecture
- The module will sit as a layer between client applications and the underlying `ourdb` & `tst` databases
- ACLs are defined at the circle level and stored in a special topic called "acl"
- Data in `ourdb` is stored at path: `~/hero/var/ourdb/$circleid/$topicid`
- `tst` is used to create mappings between keys and IDs in `ourdb`
## ACL Structure
Each ACL contains:
- A unique name (per circle)
- A list of public keys with associated permissions
- Rights are hierarchical: read → write → delete → execute → admin (each right includes all rights to its left)
## Core Methods
### ACL Management
#### aclupdate
Updates or creates an ACL with specified permissions.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the ACL exists
- `name`: Unique name for the ACL within the circle
- `pubkeys`: Array of public keys to grant permissions to
- `right`: Permission level (enum: read/write/delete/execute/admin)
#### aclremove
Removes specific public keys from an existing ACL.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the ACL exists
- `name`: Name of the ACL to modify
- `pubkeys`: Array of public keys to remove from the ACL
#### acldel
Deletes an entire ACL.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the ACL exists
- `name`: Name of the ACL to delete
### Data Operations
#### set
Stores or updates data in the database with optional ACL protection.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the data belongs
- `topic`: String identifier for the database category (e.g., "customer", "product")
- `key`: Optional string key for the record
- `id`: Optional numeric ID for direct access
- `value`: Binary blob of data to store
- `aclid`: ID of the ACL to protect this record (0 for public access)
**Behavior:**
- If only `key` is provided, use `tst` to map the key to a new or existing ID
- If `id` is specified or derived from an existing key, update the corresponding record
- Returns the ID of the created/updated record
#### del
Marks a record as deleted.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the data belongs
- `topic`: String identifier for the database category
- `id` or `key`: Identifier for the record to delete
**Behavior:**
- Deletes the mapping in `tst` if a key was used
- Marks the record as deleted in `ourdb` (not physically removed)
#### get
Retrieves data from the database.
**Parameters:**
- `callerpubkey`: Public key of the requesting user
- `circleid`: ID of the circle where the data belongs
- `topic`: String identifier for the database category
- `id` or `key`: Identifier for the record to retrieve
**Returns:**
- The binary data stored in the record if the caller has access
## Implementation Details
### ACL Storage Format
- ACLs are stored in a special topic named "acl" within each circle
- Each ACL has a unique numeric ID within the circle
### Record ACL Protection
- When a record uses ACL protection, the first 4 bytes of the stored data contain the ACL ID
- A new constructor in `ourdb` should be created to handle ACL-protected records
- Records with ACL ID of 0 are accessible to everyone
## RPC Interface
The module should expose its functionality through an RPC interface:
1. Client sends:
- Method name (e.g., "del", "set", "get")
- JSON-encoded arguments
- Cryptographic signature of the JSON data
2. Server:
- Verifies the signature is valid
- Extracts the caller's public key from the signature
- Checks permissions against applicable ACLs
- Executes the requested operation if authorized
- Returns appropriate response
## Security Considerations
- All operations must validate the caller has appropriate permissions
- ACL changes should be logged for audit purposes
- Consider implementing rate limiting to prevent abuse
## THE SERVER
- create actix webserver
- make a router that handles the rpc interface
- use openapi spec
- embed swagger interface
- implement a queuing mechanism, so internal we don't have to implement locks, but we do 1 request after the other per circle, so we know we never have conflicting changes in 1 circle
- create a logger which gives us good overview of what happened when

View File

@ -69,6 +69,9 @@ impl Wallet {
} }
} }
// Implement Storable trait
impl Storable for Wallet {}
// Implement Model trait // Implement Model trait
impl Model for Wallet { impl Model for Wallet {
fn get_id(&self) -> u32 { fn get_id(&self) -> u32 {

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError}; use crate::db::{Model, Storable, DB, DbError, DbResult};
use crate::models::gov::User; use crate::models::gov::User;
/// CommitteeRole represents the role of a member in a committee /// CommitteeRole represents the role of a member in a committee
@ -117,11 +117,11 @@ impl Committee {
} }
/// Get all users who are members of this committee /// Get all users who are members of this committee
pub fn get_member_users(&self, db: &SledDB<User>) -> Result<Vec<User>, SledDBError> { pub fn get_member_users(&self, db: &DB) -> DbResult<Vec<User>> {
let mut users = Vec::new(); let mut users = Vec::new();
for member in &self.members { for member in &self.members {
if let Ok(user) = db.get(&member.user_id.to_string()) { if let Ok(user) = db.get::<User>(member.user_id) {
users.push(user); users.push(user);
} }
} }
@ -130,14 +130,31 @@ impl Committee {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Storable trait
impl Storable for Committee {} impl Storable for Committee {
impl Storable for CommitteeMember {} fn serialize(&self) -> DbResult<Vec<u8>> {
bincode::serialize(self).map_err(DbError::SerializationError)
}
// Implement SledModel trait fn deserialize(data: &[u8]) -> DbResult<Self> {
impl SledModel for Committee { bincode::deserialize(data).map_err(DbError::SerializationError)
fn get_id(&self) -> String { }
self.id.to_string() }
impl Storable for CommitteeMember {
fn serialize(&self) -> DbResult<Vec<u8>> {
bincode::serialize(self).map_err(DbError::SerializationError)
}
fn deserialize(data: &[u8]) -> DbResult<Self> {
bincode::deserialize(data).map_err(DbError::SerializationError)
}
}
// Implement Model trait
impl Model for Committee {
fn get_id(&self) -> u32 {
self.id
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -1,4 +1,4 @@
use crate::db::{SledModel, Storable, SledDB, SledDBError}; use crate::db::{Model, Storable, DbError, DbResult};
use super::shareholder::Shareholder; // Use super:: for sibling module use super::shareholder::Shareholder; // Use super:: for sibling module
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -87,17 +87,16 @@ pub struct Company {
// Removed shareholders property // Removed shareholders property
} }
// Storable trait provides default dump/load using bincode/brotli
impl Storable for Company {}
// SledModel requires get_id and db_prefix
impl SledModel for Company { // Model requires get_id and db_prefix
fn get_id(&self) -> String { impl Model for Company {
self.id.to_string() fn get_id(&self) -> u32 {
self.id
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {
"company" // Prefix for company records in Sled "company" // Prefix for company records in the database
} }
} }
@ -138,20 +137,20 @@ impl Company {
} }
} }
/// Add a shareholder to the company, saving it to the Shareholder's SledDB /// Add a shareholder to the company, saving it to the database
pub fn add_shareholder( pub fn add_shareholder(
&mut self, &mut self,
db: &SledDB<Shareholder>, // Pass in the Shareholder's SledDB db: &mut DB, // Pass in the DB instance
mut shareholder: Shareholder, mut shareholder: Shareholder,
) -> Result<(), SledDBError> { ) -> DbResult<()> {
shareholder.company_id = self.id; // Set the company_id shareholder.company_id = self.id; // Set the company_id
db.insert(&shareholder)?; // Insert the shareholder into its own DB db.set(&shareholder)?; // Insert the shareholder into the DB
self.updated_at = Utc::now(); self.updated_at = Utc::now();
Ok(()) Ok(())
} }
/// Link this company to a Circle for access control /// Link this company to a Circle for access control
pub fn link_to_circle(&mut self, circle_id: u32) -> Result<(), SledDBError> { pub fn link_to_circle(&mut self, circle_id: u32) -> DbResult<()> {
// Implementation would involve updating a mapping in a separate database // Implementation would involve updating a mapping in a separate database
// For now, we'll just update the timestamp to indicate the change // For now, we'll just update the timestamp to indicate the change
self.updated_at = Utc::now(); self.updated_at = Utc::now();
@ -159,7 +158,7 @@ impl Company {
} }
/// Link this company to a Customer in the biz module /// Link this company to a Customer in the biz module
pub fn link_to_customer(&mut self, customer_id: u32) -> Result<(), SledDBError> { pub fn link_to_customer(&mut self, customer_id: u32) -> DbResult<()> {
// Implementation would involve updating a mapping in a separate database // Implementation would involve updating a mapping in a separate database
// For now, we'll just update the timestamp to indicate the change // For now, we'll just update the timestamp to indicate the change
self.updated_at = Utc::now(); self.updated_at = Utc::now();
@ -167,7 +166,7 @@ impl Company {
} }
/// Get all resolutions for this company /// Get all resolutions for this company
pub fn get_resolutions(&self, db: &crate::db::DB) -> Result<Vec<super::Resolution>, SledDBError> { pub fn get_resolutions(&self, db: &DB) -> DbResult<Vec<super::Resolution>> {
let all_resolutions = db.list::<super::Resolution>()?; let all_resolutions = db.list::<super::Resolution>()?;
let company_resolutions = all_resolutions let company_resolutions = all_resolutions
.into_iter() .into_iter()
@ -179,8 +178,8 @@ impl Company {
// Future methods: // Future methods:
// /// Get all committees for this company // /// Get all committees for this company
// pub fn get_committees(&self, db: &SledDB<Committee>) -> Result<Vec<Committee>, SledDBError> { ... } // pub fn get_committees(&self, db: &DB) -> DbResult<Vec<Committee>> { ... }
// //
// /// Get all compliance requirements for this company // /// Get all compliance requirements for this company
// pub fn get_compliance_requirements(&self, db: &SledDB<ComplianceRequirement>) -> Result<Vec<ComplianceRequirement>, SledDBError> { ... } // pub fn get_compliance_requirements(&self, db: &DB) -> DbResult<Vec<ComplianceRequirement>> { ... }
} }

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError}; use crate::db::{Model, Storable, DB, DbError, DbResult};
use crate::models::gov::Company; use crate::models::gov::Company;
/// ComplianceRequirement represents a regulatory requirement /// ComplianceRequirement represents a regulatory requirement
@ -82,13 +82,13 @@ impl ComplianceRequirement {
} }
/// Get the company associated with this requirement /// Get the company associated with this requirement
pub fn get_company(&self, db: &SledDB<Company>) -> Result<Company, SledDBError> { pub fn get_company(&self, db: &DB) -> DbResult<Company> {
db.get(&self.company_id.to_string()) db.get::<Company>(self.company_id)
} }
/// Get all documents associated with this requirement /// Get all documents associated with this requirement
pub fn get_documents(&self, db: &SledDB<ComplianceDocument>) -> Result<Vec<ComplianceDocument>, SledDBError> { pub fn get_documents(&self, db: &DB) -> DbResult<Vec<ComplianceDocument>> {
let all_documents = db.list()?; let all_documents = db.list::<ComplianceDocument>()?;
let requirement_documents = all_documents let requirement_documents = all_documents
.into_iter() .into_iter()
.filter(|doc| doc.requirement_id == self.id) .filter(|doc| doc.requirement_id == self.id)
@ -125,8 +125,8 @@ impl ComplianceDocument {
} }
/// Get the requirement associated with this document /// Get the requirement associated with this document
pub fn get_requirement(&self, db: &SledDB<ComplianceRequirement>) -> Result<ComplianceRequirement, SledDBError> { pub fn get_requirement(&self, db: &DB) -> DbResult<ComplianceRequirement> {
db.get(&self.requirement_id.to_string()) db.get::<ComplianceRequirement>(self.requirement_id)
} }
} }
@ -170,20 +170,15 @@ impl ComplianceAudit {
} }
/// Get the company associated with this audit /// Get the company associated with this audit
pub fn get_company(&self, db: &SledDB<Company>) -> Result<Company, SledDBError> { pub fn get_company(&self, db: &DB) -> DbResult<Company> {
db.get(&self.company_id.to_string()) db.get::<Company>(self.company_id)
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for ComplianceRequirement {} impl Model for ComplianceRequirement {
impl Storable for ComplianceDocument {} fn get_id(&self) -> u32 {
impl Storable for ComplianceAudit {} self.id
// Implement SledModel trait
impl SledModel for ComplianceRequirement {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {
@ -191,9 +186,9 @@ impl SledModel for ComplianceRequirement {
} }
} }
impl SledModel for ComplianceDocument { impl Model for ComplianceDocument {
fn get_id(&self) -> String { fn get_id(&self) -> u32 {
self.id.to_string() self.id
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {
@ -201,9 +196,9 @@ impl SledModel for ComplianceDocument {
} }
} }
impl SledModel for ComplianceAudit { impl Model for ComplianceAudit {
fn get_id(&self) -> String { fn get_id(&self) -> u32 {
self.id.to_string() self.id
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError, SledDBResult}; // Import Sled traits from db module use crate::db::{Model, Storable, DB, DbError, DbResult}; // Import traits from db module
// use std::collections::HashMap; // Removed unused import // use std::collections::HashMap; // Removed unused import
// use super::db::Model; // Removed old Model trait import // use super::db::Model; // Removed old Model trait import
@ -156,7 +156,7 @@ impl Meeting {
.collect() .collect()
} }
/// Link this meeting to a Calendar Event in the mcc module /// Link this meeting to a Calendar Event in the mcc module
pub fn link_to_event(&mut self, event_id: u32) -> Result<(), SledDBError> { pub fn link_to_event(&mut self, event_id: u32) -> DbResult<()> {
// Implementation would involve updating a mapping in a separate database // Implementation would involve updating a mapping in a separate database
// For now, we'll just update the timestamp to indicate the change // For now, we'll just update the timestamp to indicate the change
self.updated_at = Utc::now(); self.updated_at = Utc::now();
@ -164,7 +164,7 @@ impl Meeting {
} }
/// Get all resolutions discussed in this meeting /// Get all resolutions discussed in this meeting
pub fn get_resolutions(&self, db: &crate::db::DB) -> Result<Vec<super::Resolution>, SledDBError> { pub fn get_resolutions(&self, db: &DB) -> DbResult<Vec<super::Resolution>> {
let all_resolutions = db.list::<super::Resolution>()?; let all_resolutions = db.list::<super::Resolution>()?;
let meeting_resolutions = all_resolutions let meeting_resolutions = all_resolutions
.into_iter() .into_iter()
@ -175,13 +175,10 @@ impl Meeting {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for Meeting {} impl Model for Meeting {
fn get_id(&self) -> u32 {
// Implement SledModel trait self.id
impl SledModel for Meeting {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -4,9 +4,9 @@ pub mod meeting;
pub mod user; pub mod user;
pub mod vote; pub mod vote;
pub mod resolution; pub mod resolution;
// Future modules: // All modules:
// pub mod committee; pub mod committee;
// pub mod compliance; pub mod compliance;
// Re-export all model types for convenience // Re-export all model types for convenience
pub use company::{Company, CompanyStatus, BusinessType}; pub use company::{Company, CompanyStatus, BusinessType};
@ -15,6 +15,8 @@ pub use meeting::{Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus
pub use user::User; pub use user::User;
pub use vote::{Vote, VoteOption, Ballot, VoteStatus}; pub use vote::{Vote, VoteOption, Ballot, VoteStatus};
pub use resolution::{Resolution, ResolutionStatus, Approval}; pub use resolution::{Resolution, ResolutionStatus, Approval};
pub use committee::{Committee, CommitteeMember, CommitteeRole};
pub use compliance::{ComplianceRequirement, ComplianceDocument, ComplianceAudit};
// Re-export database components from db module // Re-export database components from db module
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult};

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError}; use crate::db::{Model, Storable, DB, DbError, DbResult};
use crate::models::gov::{Meeting, Vote}; use crate::models::gov::{Meeting, Vote};
/// ResolutionStatus represents the status of a resolution /// ResolutionStatus represents the status of a resolution
@ -158,10 +158,10 @@ impl Resolution {
} }
/// Get the meeting associated with this resolution /// Get the meeting associated with this resolution
pub fn get_meeting(&self, db: &crate::db::DB) -> Result<Option<Meeting>, SledDBError> { pub fn get_meeting(&self, db: &DB) -> DbResult<Option<Meeting>> {
match self.meeting_id { match self.meeting_id {
Some(meeting_id) => { Some(meeting_id) => {
let meeting = db.get::<Meeting>(&meeting_id.to_string())?; let meeting = db.get::<Meeting>(meeting_id)?;
Ok(Some(meeting)) Ok(Some(meeting))
} }
None => Ok(None), None => Ok(None),
@ -169,10 +169,10 @@ impl Resolution {
} }
/// Get the vote associated with this resolution /// Get the vote associated with this resolution
pub fn get_vote(&self, db: &crate::db::DB) -> Result<Option<Vote>, SledDBError> { pub fn get_vote(&self, db: &DB) -> DbResult<Option<Vote>> {
match self.vote_id { match self.vote_id {
Some(vote_id) => { Some(vote_id) => {
let vote = db.get::<Vote>(&vote_id.to_string())?; let vote = db.get::<Vote>(vote_id)?;
Ok(Some(vote)) Ok(Some(vote))
} }
None => Ok(None), None => Ok(None),
@ -180,14 +180,10 @@ impl Resolution {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for Resolution {} impl Model for Resolution {
impl Storable for Approval {} fn get_id(&self) -> u32 {
self.id
// Implement SledModel trait
impl SledModel for Resolution {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -1,4 +1,4 @@
use crate::db::{SledModel, Storable}; // Import Sled traits use crate::db::{Model, Storable}; // Import db traits
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// use std::collections::HashMap; // Removed unused import // use std::collections::HashMap; // Removed unused import
@ -63,13 +63,10 @@ impl Shareholder {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for Shareholder {} impl Model for Shareholder {
fn get_id(&self) -> u32 {
// Implement SledModel trait self.id
impl SledModel for Shareholder {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable}; // Import Sled traits use crate::db::{Model, Storable}; // Import db traits
// use std::collections::HashMap; // Removed unused import // use std::collections::HashMap; // Removed unused import
/// User represents a user in the governance system /// User represents a user in the governance system
@ -42,13 +42,10 @@ impl User {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for User {} impl Model for User {
fn get_id(&self) -> u32 {
// Implement SledModel trait self.id
impl SledModel for User {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -1,6 +1,6 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable, SledDB, SledDBError}; // Import Sled traits from db module use crate::db::{Model, Storable, DB, DbError, DbResult}; // Import traits from db module
// use std::collections::HashMap; // Removed unused import // use std::collections::HashMap; // Removed unused import
// use super::db::Model; // Removed old Model trait import // use super::db::Model; // Removed old Model trait import
@ -128,7 +128,7 @@ impl Vote {
} }
/// Get the resolution associated with this vote /// Get the resolution associated with this vote
pub fn get_resolution(&self, db: &crate::db::DB) -> Result<Option<super::Resolution>, SledDBError> { pub fn get_resolution(&self, db: &DB) -> DbResult<Option<super::Resolution>> {
let all_resolutions = db.list::<super::Resolution>()?; let all_resolutions = db.list::<super::Resolution>()?;
let vote_resolution = all_resolutions let vote_resolution = all_resolutions
.into_iter() .into_iter()
@ -138,13 +138,10 @@ impl Vote {
} }
} }
// Implement Storable trait (provides default dump/load) // Implement Model trait
impl Storable for Vote {} impl Model for Vote {
fn get_id(&self) -> u32 {
// Implement SledModel trait self.id
impl SledModel for Vote {
fn get_id(&self) -> String {
self.id.to_string()
} }
fn db_prefix() -> &'static str { fn db_prefix() -> &'static str {

View File

@ -4,6 +4,12 @@
Create a new module that implements an Access Control List (ACL) layer on top of the existing `ourdb` and `tst` databases. This module will manage permissions and access control for data stored in the database system. Create a new module that implements an Access Control List (ACL) layer on top of the existing `ourdb` and `tst` databases. This module will manage permissions and access control for data stored in the database system.
call this module: acldb
implement in acldb
remark: there is no dependency on herodb
## Architecture ## Architecture
- The module will sit as a layer between client applications and the underlying `ourdb` & `tst` databases - The module will sit as a layer between client applications and the underlying `ourdb` & `tst` databases
@ -18,6 +24,16 @@ Each ACL contains:
- A list of public keys with associated permissions - A list of public keys with associated permissions
- Rights are hierarchical: read → write → delete → execute → admin (each right includes all rights to its left) - Rights are hierarchical: read → write → delete → execute → admin (each right includes all rights to its left)
## factory
- returns an object with name ACLDB and is linked to 1 directory which represents a circle,
- the argument to open an ACLDB is circleid, the dir is on ~/hero/var/ourdb/$circleid
- the ACLDB has acl methods directly implemented on this one
- we have a getter on ACLDB which returns ACLDBTOPIC which is a DB isntance per topic
- on ACLDBTOPIC we have the set,get,delte, prefix, ...
- the ACLDBTOPIC knows how to get ACL's from its parent and use them
## Core Methods ## Core Methods
### ACL Management ### ACL Management
@ -124,3 +140,14 @@ The module should expose its functionality through an RPC interface:
- All operations must validate the caller has appropriate permissions - All operations must validate the caller has appropriate permissions
- ACL changes should be logged for audit purposes - ACL changes should be logged for audit purposes
- Consider implementing rate limiting to prevent abuse - Consider implementing rate limiting to prevent abuse
## THE SERVER
- create actix webserver
- make a router that handles the rpc interface
- use openapi spec
- embed swagger interface
- implement a queuing mechanism, so internal we don't have to implement locks, but we do 1 request after the other per circle, so we know we never have conflicting changes in 1 circle
- create a logger which gives us good overview of what happened when