add auth and dsl crates

This commit is contained in:
Timur Gordon 2025-06-21 09:22:53 +02:00
parent 1a3fa6242d
commit 8c88695953
13 changed files with 1777 additions and 6 deletions

29
Cargo.lock generated
View File

@ -140,6 +140,16 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "authorization"
version = "0.1.0"
dependencies = [
"heromodels",
"heromodels_core",
"rhai",
"serde",
]
[[package]]
name = "autocfg"
version = "1.4.0"
@ -2255,9 +2265,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rhai"
version = "1.22.2"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
dependencies = [
"ahash",
"bitflags 2.9.1",
@ -2364,6 +2374,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "rhailib_dsl"
version = "0.1.0"
dependencies = [
"authorization",
"chrono",
"heromodels",
"heromodels-derive",
"heromodels_core",
"rhai",
"serde",
"serde_json",
"tempfile",
]
[[package]]
name = "rhailib_worker"
version = "0.1.0"

View File

@ -39,6 +39,6 @@ members = [
"src/monitor", # Added the new monitor package to workspace
"src/repl", # Added the refactored REPL package
"examples",
"src/rhai_engine_ui",
"src/rhai_engine_ui", "src/authorization", "src/dsl",
]
resolver = "2" # Recommended for new workspaces

View File

@ -0,0 +1,10 @@
[package]
name = "authorization"
version = "0.1.0"
edition = "2024"
[dependencies]
rhai = { version = "=1.21.0", features = ["std", "sync", "decimal", "internals"] }
heromodels = { path = "../../../db/heromodels" }
heromodels_core = { path = "../../../db/heromodels_core" }
serde = { version = "1.0", features = ["derive"] }

View File

@ -0,0 +1,197 @@
//! # Rhai Authorization Crate
//! This crate provides authorization mechanisms for Rhai functions, particularly those interacting with a database.
//! It includes helper functions for authorization checks and macros to simplify the registration
//! of authorized Rhai functions.
//! ## Features:
//! - `is_super_admin`: Checks if a caller (identified by a public key) is a super admin.
//! - `can_access_resource`: Checks if a caller has specific access rights to a resource, using a database connection.
//! - `get_caller_public_key`: Helper to extract `CALLER_PUBLIC_KEY` from the Rhai `NativeCallContext`.
//! - `id_from_i64_to_u32`: Helper to convert `i64` Rhai IDs to `u32` Rust IDs.
//! - `register_authorized_get_by_id_fn!`: Macro to register a Rhai function that retrieves a single item by ID, with authorization checks.
//! - `register_authorized_list_fn!`: Macro to register a Rhai function that lists multiple items, filtering them based on authorization.
//! ## Usage:
//! 1. Use the macros to register your Rhai functions, providing a database connection (`Arc<OurDB>`) and necessary type/name information.
//! 2. The macros internally use `can_access_resource` for authorization checks.
//! 3. Ensure `CALLER_PUBLIC_KEY` is set in the Rhai engine's scope before calling authorized functions.
use heromodels::models::access::access::can_access_resource;
use heromodels_core::Model;
use rhai::{EvalAltResult, NativeCallContext, Position};
use std::convert::TryFrom;
/// Extracts the `CALLER_PUBLIC_KEY` string constant from the Rhai `NativeCallContext`.
/// This key is used to identify the caller for authorization checks.
/// It first checks the current `Scope` and then falls back to the global constants cache.
///
/// # Arguments
/// * `context`: The Rhai `NativeCallContext` of the currently executing function.
///
/// Converts an `i64` (common Rhai integer type) to a `u32` (common Rust ID type).
///
/// # Arguments
/// * `id_i64`: The `i64` value to convert.
///
/// # Errors
/// Returns `Err(EvalAltResult::ErrorMismatchDataType)` if the `i64` value cannot be represented as a `u32`.
pub fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
Box::new(EvalAltResult::ErrorMismatchDataType(
"u32".to_string(),
format!("i64 value ({}) that cannot be represented as u32", id_i64),
Position::NONE,
))
)
}
/// Extracts the `CALLER_PUBLIC_KEY` string constant from the Rhai `NativeCallContext`'s tag.
/// This key is used to identify the caller for authorization checks.
/// Macro to register a Rhai function that retrieves a single resource by its ID, with authorization.
///
/// The macro handles:
/// - Argument parsing (ID).
/// - Caller identification via `CALLER_PUBLIC_KEY`.
/// - Authorization check using `AccessControlService::can_access_resource`.
/// - Database call to fetch the resource.
/// - Error handling for type mismatches, authorization failures, DB errors, and not found errors.
///
/// # Arguments
/// * `module`: Mutable reference to the Rhai `Module`.
/// * `db_clone`: Cloned `Arc<Db>` for database access.
/// * `acs_clone`: Cloned `Arc<AccessControlService>`.
/// * `rhai_fn_name`: String literal for the Rhai function name (e.g., "get_collection").
/// * `resource_type_str`: String literal for the resource type (e.g., "Collection"), used in authorization checks and error messages.
/// * `db_method_name`: Identifier for the database method to call (e.g., `get_by_id`).
/// * `id_arg_type`: Rust type of the ID argument in Rhai (e.g., `i64`).
/// * `id_rhai_type_name`: String literal for the Rhai type name of the ID (e.g., "i64"), for error messages.
/// * `id_conversion_fn`: Path to a function converting `id_arg_type` to `actual_id_type` (e.g., `id_from_i64_to_u32`).
/// * `actual_id_type`: Rust type of the ID used in the database (e.g., `u32`).
/// * `rhai_return_rust_type`: Rust type of the resource returned by the DB and Rhai function (e.g., `RhaiCollection`).
#[macro_export]
macro_rules! register_authorized_get_by_id_fn {
(
module: $module:expr,
db_clone: $db_clone:expr, // Cloned Arc<OurDB> for database access
rhai_fn_name: $rhai_fn_name:expr, // String literal for the Rhai function name (e.g., "get_collection")
resource_type_str: $resource_type_str:expr, // String literal for the resource type (e.g., "Collection")
rhai_return_rust_type: $rhai_return_rust_type:ty // Rust type of the resource returned (e.g., `RhaiCollection`)
) => {
let db_instance_auth = $db_clone.clone();
let db_instance_fetch = $db_clone.clone();
FuncRegistration::new($rhai_fn_name).set_into_module(
$module,
move |context: rhai::NativeCallContext, id_val: i64| -> Result<Option<$rhai_return_rust_type>, Box<EvalAltResult>> {
let actual_id: u32 = $crate::id_from_i64_to_u32(id_val)?;
// Inlined logic to get caller public key
let tag_map = context
.tag()
.and_then(|tag| tag.read_lock::<rhai::Map>())
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?;
let pk_dynamic = tag_map.get("CALLER_PUBLIC_KEY")
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_PUBLIC_KEY' not found in context tag Map.".into(), context.position())))?;
let caller_pk_str = pk_dynamic.clone().into_string()?;
// Use the standalone can_access_resource function from heromodels
let has_access = heromodels::models::access::access::can_access_resource(
db_instance_auth.clone(),
&caller_pk_str,
actual_id,
$resource_type_str,
);
if !has_access {
return Ok(None);
}
let result = db_instance_fetch.get_by_id(actual_id).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Database error fetching {}: {:?}", $resource_type_str, e).into(),
context.position(),
))
})?;
Ok(result)
},
);
};
}
/// Macro to register a Rhai function that lists all resources of a certain type, with authorization.
///
/// The macro handles:
/// - Caller identification via `CALLER_PUBLIC_KEY`.
/// - Fetching all items of a specific type from the database.
/// - Filtering the items based on the standalone `can_access_resource` function for each item.
/// - Wrapping the authorized items in a specified collection type (e.g., `RhaiCollectionArray`).
/// - Error handling for DB errors during fetch or authorization checks.
///
/// # Arguments
/// * `module`: Mutable reference to the Rhai `Module`.
/// * `db_clone`: Cloned `Arc<OurDB>` for database access.
/// * `rhai_fn_name`: String literal for the Rhai function name (e.g., "list_collections").
/// * `resource_type_str`: String literal for the resource type (e.g., "Collection"), used in authorization checks.
/// * `rhai_return_rust_type`: Rust type of the resource item (e.g., `RhaiCollection`).
/// * `item_id_accessor`: Identifier for the method on `rhai_return_rust_type` that returns its ID (e.g., `id`).
/// * `rhai_return_wrapper_type`: Rust type that wraps a `Vec` of `rhai_return_rust_type` for Rhai (e.g., `RhaiCollectionArray`).
#[macro_export]
macro_rules! register_authorized_list_fn {
(
module: $module:expr,
db_clone: $db_instance:expr,
rhai_fn_name: $rhai_fn_name:expr,
resource_type_str: $resource_type_str:expr,
rhai_return_rust_type: $rhai_return_rust_type:ty,
item_id_accessor: $item_id_accessor:ident,
rhai_return_wrapper_type: $rhai_return_wrapper_type:ty
) => {
let db_instance_auth_outer = $db_instance.clone();
let db_instance_fetch = $db_instance.clone();
FuncRegistration::new($rhai_fn_name).set_into_module(
$module,
move |context: rhai::NativeCallContext| -> Result<$rhai_return_wrapper_type, Box<EvalAltResult>> {
// Inlined logic to get caller public key
let tag_map = context
.tag()
.and_then(|tag| tag.read_lock::<rhai::Map>())
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("Context tag must be a Map.".into(), context.position())))?;
let pk_dynamic = tag_map.get("CALLER_PUBLIC_KEY")
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime("'CALLER_PUBLIC_KEY' not found in context tag Map.".into(), context.position())))?;
let caller_pk_str = pk_dynamic.clone().into_string()?;
let all_items: Vec<$rhai_return_rust_type> = db_instance_fetch
.collection::<$rhai_return_rust_type>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("{:?}", e).into(), Position::NONE)))?
.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("{:?}", e).into(), Position::NONE)))?;
let authorized_items: Vec<$rhai_return_rust_type> = all_items
.into_iter()
.filter(|item| {
let resource_id = item.$item_id_accessor();
heromodels::models::access::access::can_access_resource(
db_instance_auth_outer.clone(),
&caller_pk_str,
resource_id,
$resource_type_str,
)
})
.collect();
Ok(authorized_items.into())
},
);
};
}

18
src/dsl/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "rhailib_dsl"
version = "0.1.0"
edition = "2021"
description = "Central Rhai engine for heromodels"
[dependencies]
rhai = { version = "=1.21.0", features = ["std", "sync", "decimal", "internals"] }
heromodels = { path = "../../../db/heromodels", features = ["rhai"] }
heromodels_core = { path = "../../../db/heromodels_core" }
chrono = "0.4"
heromodels-derive = { path = "../../../db/heromodels-derive" }
authorization = { path = "../authorization"}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
tempfile = "3"

View File

@ -0,0 +1,16 @@
// heromodels/examples/access/access.rhai
print("--- Testing Access Rhai Module ---");
// --- Image ---
print("\n1. Creating and saving an access...");
let access = new_access()
.object_id(1)
.circle_pk("circle_pk")
.group_id(1)
.contact_id(1)
.expires_at(10);
save_access(access);
print("Access saved with ID: " + access.id);

View File

@ -0,0 +1,39 @@
use heromodels::db::hero::OurDB;
use rhailib_dsl::access::register_access_rhai_module;
use rhai::Engine;
use std::sync::Arc;
use std::{fs, path::Path};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
let mut engine = Engine::new();
// Initialize database with OurDB
let db_path = "temp_access_db";
// Clean up previous database file if it exists
if Path::new(db_path).exists() {
fs::remove_dir_all(db_path)?;
}
let db = Arc::new(OurDB::new(db_path, true).expect("Failed to create database"));
// Register the library module with Rhai
register_access_rhai_module(&mut engine, db.clone());
// Load and evaluate the Rhai script
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let script_path = Path::new(manifest_dir).join("examples").join("access").join("access.rhai");
println!("Script path: {}", script_path.display());
let script = fs::read_to_string(&script_path)?;
println!("--- Running Access Rhai Script ---");
match engine.eval::<()>(&script) {
Ok(_) => println!("\n--- Script executed successfully! ---"),
Err(e) => eprintln!("\n--- Script execution failed: {} ---", e),
}
// Clean up the database file
fs::remove_dir_all(db_path)?;
println!("--- Cleaned up temporary database. ---");
Ok(())
}

166
src/dsl/examples/library.rs Normal file
View File

@ -0,0 +1,166 @@
use rhai::{Engine, Module, Position, Scope, Dynamic};
use std::sync::Arc;
use tempfile::tempdir;
// Import DB traits with an alias for the Collection trait to avoid naming conflicts.
// Import DB traits from heromodels::db as suggested by compiler errors.
use heromodels::db::{Db, Collection as DbCollection};
use heromodels::{
db::hero::OurDB,
models::library::collection::Collection, // Actual data model for single items
models::library::rhai::RhaiCollectionArray, // Wrapper for arrays of collections
models::access::access::Access,
};
// Import macros and the functions they depend on, which must be in scope during invocation.
use rhailib_dsl::{register_authorized_get_by_id_fn, register_authorized_list_fn};
use rhai::{FuncRegistration, EvalAltResult}; // For macro expansion
// Rewritten to match the new `Access` model structure.
fn grant_access(db: &Arc<OurDB>, user_pk: &str, resource_type: &str, resource_id: u32) {
let access_record = Access::new()
.circle_pk(user_pk.to_string())
.object_type(resource_type.to_string())
.object_id(resource_id)
.contact_id(0)
.group_id(0);
db.set(&access_record).expect("Failed to set access record");
}
// No changes needed here, but it relies on the new imports to compile.
fn register_example_module(engine: &mut Engine, db: Arc<OurDB>) {
let mut module = Module::new();
register_authorized_get_by_id_fn!(
module: &mut module,
db_clone: db.clone(),
rhai_fn_name: "get_collection",
resource_type_str: "Collection",
rhai_return_rust_type: heromodels::models::library::collection::Collection // Use Collection struct
);
register_authorized_list_fn!(
module: &mut module,
db_clone: db.clone(),
rhai_fn_name: "list_all_collections",
resource_type_str: "Collection",
rhai_return_rust_type: heromodels::models::library::collection::Collection, // Use Collection struct
item_id_accessor: id, // Assumes Collection has an id() method that returns u32
rhai_return_wrapper_type: heromodels::models::library::rhai::RhaiCollectionArray // Wrapper type for Rhai
);
engine.register_global_module(module.into());
}
fn main() -> Result<(), Box<rhai::EvalAltResult>> {
let mut engine = Engine::new();
let temp_dir = tempdir().unwrap();
let db = Arc::new(OurDB::new(temp_dir.path(), false).expect("Failed to create DB"));
register_example_module(&mut engine, db.clone());
println!("--- Registered Functions ---");
// The closure now returns Option<FuncMetadata> by cloning the metadata.
// FuncMetadata is Clone and 'static, satisfying collect_fn_metadata's requirements.
for metadata_clone in engine.collect_fn_metadata(None, |info: rhai::FuncInfo<'_>| Some(info.metadata.clone()), true) {
if metadata_clone.name == "get_collection" {
println!("Found get_collection function, args: {:?}", metadata_clone.param_types);
}
}
println!("--------------------------");
// Populate DB using the new `create_collection` helper.
// Ownership is no longer on the collection itself, so we don't need owner_pk here.
let coll = Collection::new()
.title("My new collection")
.description("This is a new collection");
db.set(&coll).expect("Failed to set collection");
let coll1 = Collection::new()
.title("Alice's Private Collection")
.description("This is Alice's private collection");
let coll2 = Collection::new()
.title("Bob's Shared Collection")
.description("This is Bob's shared collection");
let coll3 = Collection::new()
.title("General Collection")
.description("This is a general collection");
db.set(&coll1).expect("Failed to set collection");
db.set(&coll2).expect("Failed to set collection");
db.set(&coll3).expect("Failed to set collection");
// Grant access based on the new model.
grant_access(&db, "alice_pk", "Collection", coll1.id());
grant_access(&db, "bob_pk", "Collection", coll2.id());
grant_access(&db, "alice_pk", "Collection", coll2.id()); // Alice can also see Bob's collection.
grant_access(&db, "general_user_pk", "Collection", coll3.id());
println!("--- Rhai Authorization Example ---");
let mut scope = Scope::new();
// Scenario 1: Alice accesses her own collection (Success)
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
engine.set_default_tag(Dynamic::from(db_config)); // Or pass via CallFnOptions
// Create a Dynamic value holding your DB path or a config object
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
engine.set_default_tag(Dynamic::from(db_config));
println!("Alice accessing her collection 1: Success, title"); // Access field directly
let result = engine.eval::<Option<Collection>>("get_collection(1)")?;
let result_clone = result.clone().expect("REASON");
println!("Alice accessing her collection 1: Success, title = {}", result_clone.title); // Access field directly
assert_eq!(result_clone.id(), 1);
// Scenario 2: Bob tries to access Alice's collection (Failure)
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
db_config.insert("CALLER_PUBLIC_KEY".into(), "bob_pk".into());
engine.set_default_tag(Dynamic::from(db_config));
let result = engine.eval_with_scope::<Dynamic>(&mut scope, "get_collection(1)");
println!("Bob accessing Alice's collection 1: {:?}", result);
let result_clone = result.expect("REASON");
println!("Bob accessing Alice's collection 1: {:?}", result_clone);
// assert!(result_clone.is_none());
// Scenario 3: Alice accesses Bob's collection (Success)
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
engine.set_default_tag(Dynamic::from(db_config));
let result = engine.eval_with_scope::<Collection>(&mut scope, "get_collection(2)")?;
println!("Alice accessing Bob's collection 2: Success, title = {}", result.title); // Access field directly
assert_eq!(result.id(), 2);
// Scenario 4: General user lists collections (Sees 1)
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
db_config.insert("CALLER_PUBLIC_KEY".into(), "general_user_pk".into());
engine.set_default_tag(Dynamic::from(db_config));
let result = engine.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
println!("General user listing collections: Found {}", result.0.len());
assert_eq!(result.0.len(), 1);
assert_eq!(result.0[0].id(), 3);
// Scenario 5: Alice lists collections (Sees 2)
let mut db_config = rhai::Map::new();
db_config.insert("db_path".into(), "actual/path/to/db.sqlite".into());
db_config.insert("CALLER_PUBLIC_KEY".into(), "alice_pk".into());
engine.set_default_tag(Dynamic::from(db_config));
let collections = engine.eval_with_scope::<RhaiCollectionArray>(&mut scope, "list_all_collections()").unwrap();
println!("Alice listing collections: Found {}", collections.0.len());
assert_eq!(collections.0.len(), 2);
let ids: Vec<u32> = collections.0.iter().map(|c| c.id()).collect();
assert!(ids.contains(&1) && ids.contains(&2));
Ok(())
}

View File

263
src/dsl/src/access.rs Normal file
View File

@ -0,0 +1,263 @@
use heromodels::db::Db;
use rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use heromodels::models::access::Access;
type RhaiAccess = Access;
use heromodels::db::Collection;
use heromodels::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE,
))
})
}
#[export_module]
mod rhai_access_module {
// --- Access Functions ---
#[rhai_fn(name = "new_access", return_raw)]
pub fn new_access() -> Result<RhaiAccess, Box<EvalAltResult>> {
let access = Access::new();
Ok(access)
}
/// Sets the access name
#[rhai_fn(name = "object_id", return_raw, global, pure)]
pub fn set_object_id(
access: &mut RhaiAccess,
object_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = id_from_i64_to_u32(object_id)?;
let owned_access = std::mem::take(access);
*access = owned_access.object_id(id);
Ok(access.clone())
}
/// Sets the access name
#[rhai_fn(name = "circle_pk", return_raw, global, pure)]
pub fn set_circle_pk(
access: &mut RhaiAccess,
circle_pk: String,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let owned_access = std::mem::take(access);
*access = owned_access.circle_pk(circle_pk);
Ok(access.clone())
}
/// Sets the access name
#[rhai_fn(name = "group_id", return_raw, global, pure)]
pub fn set_group_id(
access: &mut RhaiAccess,
group_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = id_from_i64_to_u32(group_id)?;
let owned_access = std::mem::take(access);
*access = owned_access.group_id(id);
Ok(access.clone())
}
#[rhai_fn(name = "contact_id", return_raw, global, pure)]
pub fn set_contact_id(
access: &mut RhaiAccess,
contact_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = id_from_i64_to_u32(contact_id)?;
let owned_access = std::mem::take(access);
*access = owned_access.contact_id(id);
Ok(access.clone())
}
#[rhai_fn(name = "expires_at", return_raw, global, pure)]
pub fn set_expires_at(
access: &mut RhaiAccess,
expires_at: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let expires_at = expires_at as u64;
let owned_access = std::mem::take(access);
*access = owned_access.expires_at(Some(expires_at));
Ok(access.clone())
}
// Access Getters
#[rhai_fn(get = "id", pure)]
pub fn get_access_id(access: &mut RhaiAccess) -> i64 {
access.base_data.id as i64
}
#[rhai_fn(get = "object_id", pure)]
pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 {
access.object_id as i64
}
#[rhai_fn(get = "circle_pk", pure)]
pub fn get_access_circle_pk(access: &mut RhaiAccess) -> String {
access.circle_pk.clone()
}
#[rhai_fn(get = "group_id", pure)]
pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 {
access.group_id as i64
}
#[rhai_fn(get = "contact_id", pure)]
pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 {
access.contact_id as i64
}
#[rhai_fn(get = "expires_at", pure)]
pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 {
access.expires_at.unwrap_or(0) as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 {
access.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 {
access.base_data.modified_at
}
}
// // A function that takes the call context and an integer argument.
// fn save_access(context: NativeCallContext, access: RhaiAccess) -> Result<(), Box<EvalAltResult>> {
// let optional_tag_ref: Option<&Dynamic> = context.tag();
// // Ensure the tag exists
// let tag_ref: &Dynamic = optional_tag_ref.ok_or_else(|| {
// Box::new(EvalAltResult::ErrorRuntime(
// "Custom tag not set for this evaluation run.".into(),
// context.position(), // Use context.position() if available and relevant
// ))
// })?;
// // Initialize database with OurDB for the Rhai engine
// // Using a temporary/in-memory like database for the worker
// let tag_map = tag_ref.read_lock::<rhai::Map>().ok_or_else(|| {
// Box::new(EvalAltResult::ErrorRuntime(
// "Tag is not a Map or is locked".into(),
// Position::NONE,
// ))
// })?;
// let db_path = tag_map.get("CIRCLE_DB_PATH").expect("CIRCLE_DB_PATH not found").as_str().to_string();
// let db = Arc::new(
// OurDB::new(db_path, false)
// .expect("Failed to create temporary DB for Rhai engine"),
// );
// let result = db.set(&access).map_err(|e| {
// Box::new(EvalAltResult::ErrorRuntime(
// format!("DB Error set_access: {}", e).into(),
// Position::NONE,
// ))
// })?;
// // Return the updated access with the correct ID
// Ok(result)
// }
pub fn register_access_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally
let module = exported_module!(rhai_access_module);
engine.register_global_module(module.into());
// Create a module for database functions
let mut db_module = Module::new();
// let db_clone_set_access = db.clone();
// db_module.set_native_fn(
// "save_access",
// move |access: Access| -> Result<Access, Box<EvalAltResult>> {
// // Use the Collection trait method directly
// let result = db_clone_set_access.set(&access).map_err(|e| {
// Box::new(EvalAltResult::ErrorRuntime(
// format!("DB Error set_access: {}", e).into(),
// Position::NONE,
// ))
// })?;
// // Return the updated access with the correct ID
// Ok(result.1)
// },
// );
// Manually register database functions as they need to capture 'db'
let db_clone_delete_access = db.clone();
db_module.set_native_fn(
"delete_access",
move |access: Access| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_delete_access
.collection::<Access>()
.expect("can open access collection")
.delete_by_id(access.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_access = db.clone();
db_module.set_native_fn(
"get_access_by_id",
move |id_i64: INT| -> Result<Access, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly
db_clone_get_access
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_access_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Access with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
// Add list_accesss function to get all accesss
let db_clone_list_accesss = db.clone();
db_module.set_native_fn(
"list_accesss",
move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_accesss.collection::<Access>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get access collection: {:?}", e).into(),
Position::NONE,
))
})?;
let accesss = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all accesss: {:?}", e).into(),
Position::NONE,
))
})?;
let mut array = Array::new();
for access in accesss {
array.push(Dynamic::from(access));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally
engine.register_global_module(db_module.into());
println!("Successfully registered access Rhai module using export_module approach.");
}

6
src/dsl/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod library;
pub mod access;
pub use authorization::register_authorized_get_by_id_fn;
pub use authorization::register_authorized_list_fn;
pub use authorization::id_from_i64_to_u32;

1031
src/dsl/src/library.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,7 @@ fn seed_calendar_data(db: Arc<OurDB>) {
calendar.description = Some("My work schedule".to_string());
// Store the calendar in the database
let (calendar_id, updated_calendar) = db
let (_calendar_id, _updated_calendar) = db
.collection::<Calendar>()
.expect("Failed to get Calendar collection")
.set(&calendar)
@ -107,7 +107,7 @@ fn seed_calendar_data(db: Arc<OurDB>) {
calendar = calendar.add_event(event_id as i64);
// Store the calendar in the database
let (calendar_id, updated_calendar) = db
let (_calendar_id, updated_calendar) = db
.collection::<Calendar>()
.expect("Failed to get Calendar collection")
.set(&calendar)
@ -348,7 +348,7 @@ fn seed_finance_data(db: Arc<OurDB>) {
.add_tag("collectible".to_string());
// Store the listing in the database
let (listing_id, updated_listing) = db
let (_listing_id, updated_listing) = db
.collection::<Listing>()
.expect("Failed to get Listing collection")
.set(&listing)