This commit is contained in:
despiegk 2025-04-20 08:29:35 +02:00
parent a8ef07bb3f
commit 25983f701a
17 changed files with 348 additions and 214 deletions

View File

@ -365,7 +365,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get the calendar for an event
println!("\nGetting calendar for Family Dinner event (ID: {}):", family_dinner.id);
let event_calendar = db.get::<Calendar>(&family_dinner.calendar_id.to_string())?;
let event_calendar = db.get::<Calendar>(family_dinner.calendar_id)?;
println!(" - Calendar: {} ({})", event_calendar.title, event_calendar.description);
// Get events for a contact

View File

@ -14,8 +14,5 @@ pub use store::{DbOperations, OurDbStore};
pub mod db;
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
// Export the base module (compatibility layer for migration)
pub mod base;
// Export macros for model methods
pub mod macros;

View File

@ -1,4 +1,4 @@
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
use crate::db::{Model, Storable}; // Import Model trait from db module
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -235,13 +235,10 @@ impl ContractBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Contract {}
// Implement SledModel trait
impl SledModel for Contract {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Contract {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -1,4 +1,4 @@
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
use crate::db::{Model, Storable}; // Import Model trait from db module
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -133,13 +133,10 @@ impl CustomerBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Customer {}
// Implement SledModel trait
impl SledModel for Customer {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Customer {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -1,5 +1,5 @@
use crate::models::biz::Currency; // Use crate:: for importing from the module
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
use crate::db::{Model, Storable}; // Import Model trait from db module
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -185,10 +185,11 @@ impl Invoice {
due_date: DateTime<Utc>,
) -> Self {
let now = Utc::now();
let zero_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let zero_amount = Currency::new(
0, // Use 0 as a temporary ID for zero amounts
0.0,
currency_code.clone()
);
Self {
id,
@ -214,14 +215,16 @@ impl Invoice {
// Update the total amount
if self.items.is_empty() {
// First item, initialize the total amount with the same currency
self.total_amount = Currency {
amount: item.amount.amount,
currency_code: item.amount.currency_code.clone(),
};
self.balance_due = Currency {
amount: item.amount.amount,
currency_code: item.amount.currency_code.clone(),
};
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
item.amount.amount,
item.amount.currency_code.clone()
);
self.balance_due = Currency::new(
0, // Use 0 as a temporary ID
item.amount.amount,
item.amount.currency_code.clone()
);
} else {
// Add to the existing total
// (Assumes all items have the same currency)
@ -252,10 +255,11 @@ impl Invoice {
}
// Update the total amount
self.total_amount = Currency {
amount: total,
currency_code: currency_code.clone(),
};
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
total,
currency_code.clone()
);
// Recalculate the balance due
self.calculate_balance();
@ -290,10 +294,11 @@ impl Invoice {
}
// Update the balance due
self.balance_due = Currency {
amount: balance,
currency_code: self.total_amount.currency_code.clone(),
};
self.balance_due = Currency::new(
0, // Use 0 as a temporary ID
balance,
self.total_amount.currency_code.clone()
);
// Update the payment status
self.update_payment_status();
@ -438,10 +443,11 @@ impl InvoiceBuilder {
let currency_code = self.currency_code.ok_or("currency_code is required")?;
// Initialize with empty total amount and balance due
let mut total_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let mut total_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
// Calculate total amount from items
for item in &self.items {
@ -494,13 +500,10 @@ impl InvoiceBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Invoice {}
// Implement SledModel trait
impl SledModel for Invoice {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Invoice {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -26,5 +26,5 @@ pub use product::Currency;
pub use currency::CurrencyBuilder;
// Re-export database components
// Re-export from core module
pub use crate::core::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
// Re-export database components from db module
pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult, ModelRegistration, ModelRegistrar};

View File

@ -1,5 +1,5 @@
use crate::db::base::{SledModel, Storable};
use crate::models::biz::Currency; // Use crate:: for importing from the module // Import Sled traits from db module
use crate::db::{Model, Storable};
use crate::models::biz::Currency; // Use crate:: for importing from the module
// use super::db::Model; // Removed old Model trait import
use chrono::{DateTime, Utc};
use rhai::{CustomType, TypeBuilder};
@ -47,17 +47,19 @@ impl SaleItem {
) -> Self {
// Calculate subtotal (before tax)
let amount = unit_price.amount * quantity as f64;
let subtotal = Currency {
let subtotal = Currency::new(
0, // Use 0 as a temporary ID
amount,
currency_code: unit_price.currency_code.clone(),
};
unit_price.currency_code.clone()
);
// Calculate tax amount
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
let tax_amount = Currency {
amount: tax_amount_value,
currency_code: unit_price.currency_code.clone(),
};
let tax_amount = Currency::new(
0, // Use 0 as a temporary ID
tax_amount_value,
unit_price.currency_code.clone()
);
Self {
id,
@ -77,10 +79,11 @@ impl SaleItem {
/// Get the total amount including tax
pub fn total_with_tax(&self) -> Currency {
Currency {
amount: self.subtotal.amount + self.tax_amount.amount,
currency_code: self.subtotal.currency_code.clone(),
}
Currency::new(
0, // Use 0 as a temporary ID
self.subtotal.amount + self.tax_amount.amount,
self.subtotal.currency_code.clone()
)
}
}
@ -188,17 +191,19 @@ impl SaleItemBuilder {
// Calculate subtotal
let amount = unit_price.amount * quantity as f64;
let subtotal = Currency {
let subtotal = Currency::new(
0, // Use 0 as a temporary ID
amount,
currency_code: unit_price.currency_code.clone(),
};
unit_price.currency_code.clone()
);
// Calculate tax amount
let tax_amount_value = subtotal.amount * (tax_rate / 100.0);
let tax_amount = Currency {
amount: tax_amount_value,
currency_code: unit_price.currency_code.clone(),
};
let tax_amount = Currency::new(
0, // Use 0 as a temporary ID
tax_amount_value,
unit_price.currency_code.clone()
);
Ok(SaleItem {
id: self.id.ok_or("id is required")?,
@ -250,10 +255,11 @@ impl Sale {
status: SaleStatus,
) -> Self {
let now = Utc::now();
let zero_currency = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let zero_currency = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
Self {
id,
@ -281,18 +287,21 @@ impl Sale {
// Update the amounts
if self.items.is_empty() {
// First item, initialize the amounts with the same currency
self.subtotal_amount = Currency {
amount: item.subtotal.amount,
currency_code: item.subtotal.currency_code.clone(),
};
self.tax_amount = Currency {
amount: item.tax_amount.amount,
currency_code: item.tax_amount.currency_code.clone(),
};
self.total_amount = Currency {
amount: item.subtotal.amount + item.tax_amount.amount,
currency_code: item.subtotal.currency_code.clone(),
};
self.subtotal_amount = Currency::new(
0, // Use 0 as a temporary ID
item.subtotal.amount,
item.subtotal.currency_code.clone()
);
self.tax_amount = Currency::new(
0, // Use 0 as a temporary ID
item.tax_amount.amount,
item.tax_amount.currency_code.clone()
);
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
item.subtotal.amount + item.tax_amount.amount,
item.subtotal.currency_code.clone()
);
} else {
// Add to the existing totals
// (Assumes all items have the same currency)
@ -327,18 +336,21 @@ impl Sale {
}
// Update the amounts
self.subtotal_amount = Currency {
amount: subtotal,
currency_code: currency_code.clone(),
};
self.tax_amount = Currency {
amount: tax_total,
currency_code: currency_code.clone(),
};
self.total_amount = Currency {
amount: subtotal + tax_total,
currency_code,
};
self.subtotal_amount = Currency::new(
0, // Use 0 as a temporary ID
subtotal,
currency_code.clone()
);
self.tax_amount = Currency::new(
0, // Use 0 as a temporary ID
tax_total,
currency_code.clone()
);
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
subtotal + tax_total,
currency_code
);
// Update the timestamp
self.updated_at = Utc::now();
@ -505,18 +517,21 @@ impl SaleBuilder {
let currency_code = self.currency_code.ok_or("currency_code is required")?;
// Initialize with empty amounts
let mut subtotal_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let mut tax_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let mut total_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let mut subtotal_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
let mut tax_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
let mut total_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
// Calculate amounts from items
for item in &self.items {
@ -551,16 +566,14 @@ impl SaleBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Sale {}
// Implement SledModel trait
impl SledModel for Sale {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Sale {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {
"sale"
}
}

View File

@ -1,5 +1,5 @@
use crate::models::biz::Currency; // Use crate:: for importing from the module
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
use crate::db::{Model, Storable}; // Import Model trait from db module
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@ -57,22 +57,25 @@ impl ServiceItem {
) -> Self {
// Calculate subtotal
let amount = unit_price.amount * quantity as f64;
let subtotal = Currency {
let subtotal = Currency::new(
0, // Use 0 as a temporary ID
amount,
currency_code: unit_price.currency_code.clone(),
};
unit_price.currency_code.clone()
);
// Calculate tax amount if taxable
let tax_amount = if is_taxable {
Currency {
amount: subtotal.amount * tax_rate,
currency_code: unit_price.currency_code.clone(),
}
Currency::new(
0, // Use 0 as a temporary ID
subtotal.amount * tax_rate,
unit_price.currency_code.clone()
)
} else {
Currency {
amount: 0.0,
currency_code: unit_price.currency_code.clone(),
}
Currency::new(
0, // Use 0 as a temporary ID
0.0,
unit_price.currency_code.clone()
)
};
Self {
@ -95,24 +98,27 @@ impl ServiceItem {
/// Calculate the subtotal based on quantity and unit price
pub fn calculate_subtotal(&mut self) {
let amount = self.unit_price.amount * self.quantity as f64;
self.subtotal = Currency {
self.subtotal = Currency::new(
0, // Use 0 as a temporary ID
amount,
currency_code: self.unit_price.currency_code.clone(),
};
self.unit_price.currency_code.clone()
);
}
/// Calculate the tax amount based on subtotal and tax rate
pub fn calculate_tax(&mut self) {
if self.is_taxable {
self.tax_amount = Currency {
amount: self.subtotal.amount * self.tax_rate,
currency_code: self.subtotal.currency_code.clone(),
};
self.tax_amount = Currency::new(
0, // Use 0 as a temporary ID
self.subtotal.amount * self.tax_rate,
self.subtotal.currency_code.clone()
);
} else {
self.tax_amount = Currency {
amount: 0.0,
currency_code: self.subtotal.currency_code.clone(),
};
self.tax_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
self.subtotal.currency_code.clone()
);
}
}
}
@ -229,22 +235,25 @@ impl ServiceItemBuilder {
// Calculate subtotal
let amount = unit_price.amount * quantity as f64;
let subtotal = Currency {
let subtotal = Currency::new(
0, // Use 0 as a temporary ID
amount,
currency_code: unit_price.currency_code.clone(),
};
unit_price.currency_code.clone()
);
// Calculate tax amount if taxable
let tax_amount = if is_taxable {
Currency {
amount: subtotal.amount * tax_rate,
currency_code: unit_price.currency_code.clone(),
}
Currency::new(
0, // Use 0 as a temporary ID
subtotal.amount * tax_rate,
unit_price.currency_code.clone()
)
} else {
Currency {
amount: 0.0,
currency_code: unit_price.currency_code.clone(),
}
Currency::new(
0, // Use 0 as a temporary ID
0.0,
unit_price.currency_code.clone()
)
};
Ok(ServiceItem {
@ -292,7 +301,7 @@ impl Service {
Self {
id,
customer_id,
total_amount: Currency { amount: 0.0, currency_code },
total_amount: Currency::new(0, 0.0, currency_code),
status,
billing_frequency,
service_date: now,
@ -310,10 +319,11 @@ impl Service {
// Update the total amount
if self.items.is_empty() {
// First item, initialize the total amount with the same currency
self.total_amount = Currency {
amount: item.subtotal.amount + item.tax_amount.amount,
currency_code: item.subtotal.currency_code.clone(),
};
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
item.subtotal.amount + item.tax_amount.amount,
item.subtotal.currency_code.clone()
);
} else {
// Add to the existing total
// (Assumes all items have the same currency)
@ -343,10 +353,11 @@ impl Service {
}
// Update the total amount
self.total_amount = Currency {
amount: total,
currency_code,
};
self.total_amount = Currency::new(
0, // Use 0 as a temporary ID
total,
currency_code
);
// Update the service timestamp
self.updated_at = Utc::now();
@ -439,10 +450,11 @@ impl ServiceBuilder {
let currency_code = self.currency_code.ok_or("currency_code is required")?;
// Initialize with empty total amount
let mut total_amount = Currency {
amount: 0.0,
currency_code: currency_code.clone(),
};
let mut total_amount = Currency::new(
0, // Use 0 as a temporary ID
0.0,
currency_code.clone()
);
// Calculate total amount from items
for item in &self.items {
@ -453,10 +465,11 @@ impl ServiceBuilder {
if total_amount.amount == 0.0 {
// First item, initialize the total amount with the same currency
total_amount = Currency {
amount: item.subtotal.amount + item.tax_amount.amount,
currency_code: item.subtotal.currency_code.clone(),
};
total_amount = Currency::new(
0, // Use 0 as a temporary ID
item.subtotal.amount + item.tax_amount.amount,
item.subtotal.currency_code.clone()
);
} else {
// Add to the existing total
// (Assumes all items have the same currency)
@ -478,13 +491,10 @@ impl ServiceBuilder {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Service {}
// Implement SledModel trait
impl SledModel for Service {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Service {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable};
use crate::db::{Model, Storable};
use std::collections::HashMap;
/// Circle represents a collection of members (users or other circles)
@ -28,13 +28,10 @@ impl Circle {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Circle {}
// Implement SledModel trait
impl SledModel for Circle {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Circle {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -6,4 +6,4 @@ pub use circle::{Circle, Member, Role};
pub use name::{Name, Record, RecordType};
// Re-export database components
pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB};
pub use crate::db::{DB, DBBuilder, Model, Storable, DbError, DbResult, ModelRegistration, ModelRegistrar};

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable};
use crate::db::{Model, Storable};
use std::collections::HashMap;
/// Role represents the role of a member in a circle
@ -67,13 +67,10 @@ impl Member {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Member {}
// Implement SledModel trait
impl SledModel for Member {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Member {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -10,4 +10,4 @@ pub use name::{Name, Record, RecordType};
pub use wallet::{Wallet, Asset};
// 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, ModelRegistration, ModelRegistrar};

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable};
use crate::db::{Model, Storable};
/// Record types for a DNS record
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -57,13 +57,10 @@ impl Name {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Name {}
// Implement SledModel trait
impl SledModel for Name {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Name {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::db::{SledModel, Storable};
use crate::db::{Model, Storable};
use std::collections::HashMap;
/// Asset represents a cryptocurrency asset in a wallet
@ -69,13 +69,10 @@ impl Wallet {
}
}
// Implement Storable trait (provides default dump/load)
impl Storable for Wallet {}
// Implement SledModel trait
impl SledModel for Wallet {
fn get_id(&self) -> String {
self.id.to_string()
// Implement Model trait
impl Model for Wallet {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {

View File

@ -2,7 +2,10 @@ in @src/models/circle/circle.rs
- member us now new rootobject, check implementation
- a member is linked to one or more contacts id's (from src/models/mcc/contacts.rs)
- a member has one or more wallets
- create a new rootobject called wallet
- has a name, description, blockchainname (string), pubkey
- a wallet has embedded struct for asset which is name e.g. USDC and float which is the amount of money in the asset
- a member has one or more wallets, in member link to the id's of the wallet
in@src/models/biz add a ticket module

126
instructions.md Normal file
View File

@ -0,0 +1,126 @@
# 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.
## 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