diff --git a/acldb/Cargo.toml b/acldb/Cargo.toml new file mode 100644 index 0000000..26e51dc --- /dev/null +++ b/acldb/Cargo.toml @@ -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" diff --git a/herodb/src/cmd/dbexample_gov/main.rs b/herodb/src/cmd/dbexample_gov/main.rs index 1578a90..9718618 100644 --- a/herodb/src/cmd/dbexample_gov/main.rs +++ b/herodb/src/cmd/dbexample_gov/main.rs @@ -1,12 +1,14 @@ use chrono::{Utc, Duration}; -use herodb::db::{DBBuilder, SledDB, SledModel}; +use herodb::db::{DBBuilder, DB}; use herodb::models::gov::{ Company, CompanyStatus, BusinessType, Shareholder, ShareholderType, Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus, User, Vote, VoteOption, Ballot, VoteStatus, - Resolution, ResolutionStatus, Approval + Resolution, ResolutionStatus, Approval, + Committee, CommitteeRole, CommitteeMember, + ComplianceRequirement, ComplianceDocument, ComplianceAudit }; use std::path::PathBuf; use std::fs; @@ -31,6 +33,10 @@ fn main() -> Result<(), Box> { .register_model::() .register_model::() .register_model::() + .register_model::() + .register_model::() + .register_model::() + .register_model::() .build()?; println!("\n1. Creating a Company"); @@ -325,7 +331,7 @@ fn main() -> Result<(), Box> { println!("---------------------------"); // Retrieve company and related objects - let retrieved_company = db.get::(&company.id.to_string())?; + let retrieved_company = db.get::(company.id)?; println!("Company: {} (ID: {})", retrieved_company.name, retrieved_company.id); // Get resolutions for this company @@ -336,7 +342,7 @@ fn main() -> Result<(), Box> { } // Get meeting and its resolutions - let retrieved_meeting = db.get::(&meeting.id.to_string())?; + let retrieved_meeting = db.get::(meeting.id)?; println!("Meeting: {} ({})", retrieved_meeting.title, retrieved_meeting.date.format("%Y-%m-%d")); let meeting_resolutions = retrieved_meeting.get_resolutions(&db)?; @@ -346,7 +352,7 @@ fn main() -> Result<(), Box> { } // Get vote and its resolution - let retrieved_vote = db.get::(&vote.id.to_string())?; + let retrieved_vote = db.get::(vote.id)?; println!("Vote: {} (Status: {:?})", retrieved_vote.title, retrieved_vote.status); if let Ok(Some(vote_res)) = retrieved_vote.get_resolution(&db) { @@ -354,7 +360,7 @@ fn main() -> Result<(), Box> { } // Get resolution and its related objects - let retrieved_resolution = db.get::(&resolution.id.to_string())?; + let retrieved_resolution = db.get::(resolution.id)?; println!("Resolution: {} (Status: {:?})", retrieved_resolution.title, retrieved_resolution.status); if let Ok(Some(res_meeting)) = retrieved_resolution.get_meeting(&db) { diff --git a/herodb/src/db/model.rs b/herodb/src/db/model.rs index 3ba4f33..dd3a612 100644 --- a/herodb/src/db/model.rs +++ b/herodb/src/db/model.rs @@ -26,5 +26,5 @@ pub trait Model: Storable + Debug + Clone + Send + Sync + 'static { fn db_prefix() -> &'static str; } -// Implement Storable for common types that might be used in models -impl Deserialize<'de> + Sized> Storable for T {} \ No newline at end of file +// Note: We don't provide a blanket implementation of Storable +// Each model type must implement Storable explicitly \ No newline at end of file diff --git a/herodb/src/db/model_methods.rs b/herodb/src/db/model_methods.rs index 09eac16..b6d338b 100644 --- a/herodb/src/db/model_methods.rs +++ b/herodb/src/db/model_methods.rs @@ -2,6 +2,10 @@ use crate::db::db::DB; use crate::db::model::Model; use crate::impl_model_methods; 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 impl_model_methods!(Product, product, products); @@ -25,4 +29,34 @@ impl_model_methods!(Customer, customer, customers); impl_model_methods!(Contract, contract, contracts); // Implement model-specific methods for Invoice -impl_model_methods!(Invoice, invoice, invoices); \ No newline at end of file +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); \ No newline at end of file diff --git a/herodb/src/instructions.md b/herodb/src/instructions.md new file mode 100644 index 0000000..bc25828 --- /dev/null +++ b/herodb/src/instructions.md @@ -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 + + diff --git a/herodb/src/models/circle/wallet.rs b/herodb/src/models/circle/wallet.rs index da187e6..af3f4c1 100644 --- a/herodb/src/models/circle/wallet.rs +++ b/herodb/src/models/circle/wallet.rs @@ -69,6 +69,9 @@ impl Wallet { } } +// Implement Storable trait +impl Storable for Wallet {} + // Implement Model trait impl Model for Wallet { fn get_id(&self) -> u32 { diff --git a/herodb/src/models/gov/committee.rs b/herodb/src/models/gov/committee.rs index c7977f9..5ec66b5 100644 --- a/herodb/src/models/gov/committee.rs +++ b/herodb/src/models/gov/committee.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::db::{SledModel, Storable, SledDB, SledDBError}; +use crate::db::{Model, Storable, DB, DbError, DbResult}; use crate::models::gov::User; /// 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 - pub fn get_member_users(&self, db: &SledDB) -> Result, SledDBError> { + pub fn get_member_users(&self, db: &DB) -> DbResult> { let mut users = Vec::new(); for member in &self.members { - if let Ok(user) = db.get(&member.user_id.to_string()) { + if let Ok(user) = db.get::(member.user_id) { users.push(user); } } @@ -130,14 +130,31 @@ impl Committee { } } -// Implement Storable trait (provides default dump/load) -impl Storable for Committee {} -impl Storable for CommitteeMember {} +// Implement Storable trait +impl Storable for Committee { + fn serialize(&self) -> DbResult> { + bincode::serialize(self).map_err(DbError::SerializationError) + } + + fn deserialize(data: &[u8]) -> DbResult { + bincode::deserialize(data).map_err(DbError::SerializationError) + } +} -// Implement SledModel trait -impl SledModel for Committee { - fn get_id(&self) -> String { - self.id.to_string() +impl Storable for CommitteeMember { + fn serialize(&self) -> DbResult> { + bincode::serialize(self).map_err(DbError::SerializationError) + } + + fn deserialize(data: &[u8]) -> DbResult { + 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 { diff --git a/herodb/src/models/gov/company.rs b/herodb/src/models/gov/company.rs index 3c35fad..2f166dc 100644 --- a/herodb/src/models/gov/company.rs +++ b/herodb/src/models/gov/company.rs @@ -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 chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -87,17 +87,16 @@ pub struct Company { // 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 { - fn get_id(&self) -> String { - self.id.to_string() + +// Model requires get_id and db_prefix +impl Model for Company { + fn get_id(&self) -> u32 { + self.id } 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( &mut self, - db: &SledDB, // Pass in the Shareholder's SledDB + db: &mut DB, // Pass in the DB instance mut shareholder: Shareholder, - ) -> Result<(), SledDBError> { + ) -> DbResult<()> { 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(); Ok(()) } /// 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 // For now, we'll just update the timestamp to indicate the change self.updated_at = Utc::now(); @@ -159,7 +158,7 @@ impl Company { } /// 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 // For now, we'll just update the timestamp to indicate the change self.updated_at = Utc::now(); @@ -167,7 +166,7 @@ impl Company { } /// Get all resolutions for this company - pub fn get_resolutions(&self, db: &crate::db::DB) -> Result, SledDBError> { + pub fn get_resolutions(&self, db: &DB) -> DbResult> { let all_resolutions = db.list::()?; let company_resolutions = all_resolutions .into_iter() @@ -179,8 +178,8 @@ impl Company { // Future methods: // /// Get all committees for this company - // pub fn get_committees(&self, db: &SledDB) -> Result, SledDBError> { ... } + // pub fn get_committees(&self, db: &DB) -> DbResult> { ... } // // /// Get all compliance requirements for this company - // pub fn get_compliance_requirements(&self, db: &SledDB) -> Result, SledDBError> { ... } + // pub fn get_compliance_requirements(&self, db: &DB) -> DbResult> { ... } } diff --git a/herodb/src/models/gov/compliance.rs b/herodb/src/models/gov/compliance.rs index 9f8063e..e471ae4 100644 --- a/herodb/src/models/gov/compliance.rs +++ b/herodb/src/models/gov/compliance.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::db::{SledModel, Storable, SledDB, SledDBError}; +use crate::db::{Model, Storable, DB, DbError, DbResult}; use crate::models::gov::Company; /// ComplianceRequirement represents a regulatory requirement @@ -82,13 +82,13 @@ impl ComplianceRequirement { } /// Get the company associated with this requirement - pub fn get_company(&self, db: &SledDB) -> Result { - db.get(&self.company_id.to_string()) + pub fn get_company(&self, db: &DB) -> DbResult { + db.get::(self.company_id) } /// Get all documents associated with this requirement - pub fn get_documents(&self, db: &SledDB) -> Result, SledDBError> { - let all_documents = db.list()?; + pub fn get_documents(&self, db: &DB) -> DbResult> { + let all_documents = db.list::()?; let requirement_documents = all_documents .into_iter() .filter(|doc| doc.requirement_id == self.id) @@ -125,8 +125,8 @@ impl ComplianceDocument { } /// Get the requirement associated with this document - pub fn get_requirement(&self, db: &SledDB) -> Result { - db.get(&self.requirement_id.to_string()) + pub fn get_requirement(&self, db: &DB) -> DbResult { + db.get::(self.requirement_id) } } @@ -170,20 +170,15 @@ impl ComplianceAudit { } /// Get the company associated with this audit - pub fn get_company(&self, db: &SledDB) -> Result { - db.get(&self.company_id.to_string()) + pub fn get_company(&self, db: &DB) -> DbResult { + db.get::(self.company_id) } } -// Implement Storable trait (provides default dump/load) -impl Storable for ComplianceRequirement {} -impl Storable for ComplianceDocument {} -impl Storable for ComplianceAudit {} - -// Implement SledModel trait -impl SledModel for ComplianceRequirement { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for ComplianceRequirement { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { @@ -191,9 +186,9 @@ impl SledModel for ComplianceRequirement { } } -impl SledModel for ComplianceDocument { - fn get_id(&self) -> String { - self.id.to_string() +impl Model for ComplianceDocument { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { @@ -201,9 +196,9 @@ impl SledModel for ComplianceDocument { } } -impl SledModel for ComplianceAudit { - fn get_id(&self) -> String { - self.id.to_string() +impl Model for ComplianceAudit { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/herodb/src/models/gov/meeting.rs b/herodb/src/models/gov/meeting.rs index c355840..a5901e4 100644 --- a/herodb/src/models/gov/meeting.rs +++ b/herodb/src/models/gov/meeting.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; 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 super::db::Model; // Removed old Model trait import @@ -156,7 +156,7 @@ impl Meeting { .collect() } /// 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 // For now, we'll just update the timestamp to indicate the change self.updated_at = Utc::now(); @@ -164,7 +164,7 @@ impl Meeting { } /// Get all resolutions discussed in this meeting - pub fn get_resolutions(&self, db: &crate::db::DB) -> Result, SledDBError> { + pub fn get_resolutions(&self, db: &DB) -> DbResult> { let all_resolutions = db.list::()?; let meeting_resolutions = all_resolutions .into_iter() @@ -175,13 +175,10 @@ impl Meeting { } } -// Implement Storable trait (provides default dump/load) -impl Storable for Meeting {} - -// Implement SledModel trait -impl SledModel for Meeting { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for Meeting { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/herodb/src/models/gov/mod.rs b/herodb/src/models/gov/mod.rs index de1187e..5b1dd7b 100644 --- a/herodb/src/models/gov/mod.rs +++ b/herodb/src/models/gov/mod.rs @@ -4,9 +4,9 @@ pub mod meeting; pub mod user; pub mod vote; pub mod resolution; -// Future modules: -// pub mod committee; -// pub mod compliance; +// All modules: +pub mod committee; +pub mod compliance; // Re-export all model types for convenience pub use company::{Company, CompanyStatus, BusinessType}; @@ -15,6 +15,8 @@ pub use meeting::{Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus pub use user::User; pub use vote::{Vote, VoteOption, Ballot, VoteStatus}; 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 -pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; \ No newline at end of file +pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult}; \ No newline at end of file diff --git a/herodb/src/models/gov/resolution.rs b/herodb/src/models/gov/resolution.rs index 1416e35..e0aa282 100644 --- a/herodb/src/models/gov/resolution.rs +++ b/herodb/src/models/gov/resolution.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; 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}; /// ResolutionStatus represents the status of a resolution @@ -158,10 +158,10 @@ impl Resolution { } /// Get the meeting associated with this resolution - pub fn get_meeting(&self, db: &crate::db::DB) -> Result, SledDBError> { + pub fn get_meeting(&self, db: &DB) -> DbResult> { match self.meeting_id { Some(meeting_id) => { - let meeting = db.get::(&meeting_id.to_string())?; + let meeting = db.get::(meeting_id)?; Ok(Some(meeting)) } None => Ok(None), @@ -169,10 +169,10 @@ impl Resolution { } /// Get the vote associated with this resolution - pub fn get_vote(&self, db: &crate::db::DB) -> Result, SledDBError> { + pub fn get_vote(&self, db: &DB) -> DbResult> { match self.vote_id { Some(vote_id) => { - let vote = db.get::(&vote_id.to_string())?; + let vote = db.get::(vote_id)?; Ok(Some(vote)) } None => Ok(None), @@ -180,14 +180,10 @@ impl Resolution { } } -// Implement Storable trait (provides default dump/load) -impl Storable for Resolution {} -impl Storable for Approval {} - -// Implement SledModel trait -impl SledModel for Resolution { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for Resolution { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/herodb/src/models/gov/shareholder.rs b/herodb/src/models/gov/shareholder.rs index 00c6be5..19e1c97 100644 --- a/herodb/src/models/gov/shareholder.rs +++ b/herodb/src/models/gov/shareholder.rs @@ -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 serde::{Deserialize, Serialize}; // use std::collections::HashMap; // Removed unused import @@ -63,13 +63,10 @@ impl Shareholder { } } -// Implement Storable trait (provides default dump/load) -impl Storable for Shareholder {} - -// Implement SledModel trait -impl SledModel for Shareholder { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for Shareholder { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/herodb/src/models/gov/user.rs b/herodb/src/models/gov/user.rs index 51f9a8e..b55931c 100644 --- a/herodb/src/models/gov/user.rs +++ b/herodb/src/models/gov/user.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; 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 /// User represents a user in the governance system @@ -42,13 +42,10 @@ impl User { } } -// Implement Storable trait (provides default dump/load) -impl Storable for User {} - -// Implement SledModel trait -impl SledModel for User { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for User { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/herodb/src/models/gov/vote.rs b/herodb/src/models/gov/vote.rs index 9e4cc4a..0025097 100644 --- a/herodb/src/models/gov/vote.rs +++ b/herodb/src/models/gov/vote.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; 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 super::db::Model; // Removed old Model trait import @@ -128,7 +128,7 @@ impl Vote { } /// Get the resolution associated with this vote - pub fn get_resolution(&self, db: &crate::db::DB) -> Result, SledDBError> { + pub fn get_resolution(&self, db: &DB) -> DbResult> { let all_resolutions = db.list::()?; let vote_resolution = all_resolutions .into_iter() @@ -138,13 +138,10 @@ impl Vote { } } -// Implement Storable trait (provides default dump/load) -impl Storable for Vote {} - -// Implement SledModel trait -impl SledModel for Vote { - fn get_id(&self) -> String { - self.id.to_string() +// Implement Model trait +impl Model for Vote { + fn get_id(&self) -> u32 { + self.id } fn db_prefix() -> &'static str { diff --git a/instructions.md b/instructions.md index de7617c..198a9f9 100644 --- a/instructions.md +++ b/instructions.md @@ -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. +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 @@ -18,6 +24,16 @@ Each ACL contains: - A list of public keys with associated permissions - 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 ### 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 - 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 + +