Compare commits

5 Commits

Author SHA1 Message Date
Timur Gordon
6569e819ae marketplace models wip 2025-08-21 14:07:14 +02:00
Timur Gordon
130822b69b Merge branch 'development' of https://git.ourworld.tf/herocode/db into development 2025-08-21 14:06:40 +02:00
Timur Gordon
7439980b33 Merge branch 'main' into development 2025-08-21 14:05:57 +02:00
Timur Gordon
cedea2f305 move rhai wrappers of models from rhailib 2025-08-21 14:05:01 +02:00
Maxime Van Hees
453e86edd2 fixed dependencies issues on main branch which is called 'development' 2025-08-05 13:11:53 +02:00
36 changed files with 10111 additions and 427 deletions

274
Cargo.lock generated
View File

@@ -17,6 +17,17 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.16",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.12"
@@ -60,7 +71,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -116,6 +127,18 @@ version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -125,12 +148,57 @@ dependencies = [
"generic-array",
]
[[package]]
name = "borsh"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
dependencies = [
"borsh-derive",
"cfg_aliases",
]
[[package]]
name = "borsh-derive"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3"
dependencies = [
"once_cell",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytecheck"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
dependencies = [
"bytecheck_derive",
"ptr_meta",
"simdutf8",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -158,6 +226,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.41"
@@ -268,6 +342,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -292,7 +372,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -361,6 +441,15 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.8",
]
[[package]]
name = "hashbrown"
version = "0.15.4"
@@ -387,6 +476,8 @@ dependencies = [
"r2d2",
"r2d2_postgres",
"rhai",
"rhailib-macros",
"rust_decimal",
"serde",
"serde_json",
"strum",
@@ -403,7 +494,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -454,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.15.4",
]
[[package]]
@@ -506,7 +597,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -804,6 +895,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -813,6 +913,26 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "quote"
version = "1.0.40"
@@ -849,6 +969,12 @@ dependencies = [
"r2d2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@@ -917,13 +1043,22 @@ dependencies = [
"bitflags",
]
[[package]]
name = "rend"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
dependencies = [
"bytecheck",
]
[[package]]
name = "rhai"
version = "1.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
dependencies = [
"ahash",
"ahash 0.8.12",
"bitflags",
"instant",
"no-std-compat",
@@ -944,7 +1079,44 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
name = "rhailib-macros"
version = "0.1.0"
dependencies = [
"rhai",
"serde",
]
[[package]]
name = "rkyv"
version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
dependencies = [
"bitvec",
"bytecheck",
"bytes",
"hashbrown 0.12.3",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
"tinyvec",
"uuid",
]
[[package]]
name = "rkyv_derive"
version = "0.7.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
@@ -954,7 +1126,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d"
dependencies = [
"arrayvec",
"borsh",
"bytes",
"num-traits",
"rand 0.8.5",
"rkyv",
"serde",
"serde_json",
]
[[package]]
@@ -990,6 +1168,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.219"
@@ -1007,7 +1191,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1049,6 +1233,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.1"
@@ -1137,7 +1327,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1146,6 +1336,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.104"
@@ -1157,6 +1358,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thin-vec"
version = "0.2.14"
@@ -1180,7 +1387,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1235,7 +1442,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1277,6 +1484,23 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "tst"
version = "0.1.0"
@@ -1390,7 +1614,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -1412,7 +1636,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1468,7 +1692,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1479,7 +1703,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1588,6 +1812,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
@@ -1597,6 +1830,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zerocopy"
version = "0.8.26"
@@ -1614,5 +1856,5 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]

48
heromodels/Cargo.lock generated
View File

@@ -60,7 +60,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -233,14 +233,6 @@ dependencies = [
"typenum",
]
[[package]]
name = "derive"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -300,7 +292,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -387,7 +379,6 @@ version = "0.1.0"
dependencies = [
"bincode",
"chrono",
"derive",
"heromodels-derive",
"heromodels_core",
"jsonb",
@@ -411,7 +402,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -514,7 +505,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -952,7 +943,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1015,7 +1006,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1145,7 +1136,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1154,17 +1145,6 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.104"
@@ -1199,7 +1179,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1254,7 +1234,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1409,7 +1389,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
"wasm-bindgen-shared",
]
@@ -1431,7 +1411,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1487,7 +1467,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1498,7 +1478,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]
[[package]]
@@ -1633,5 +1613,5 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn",
]

View File

@@ -14,12 +14,14 @@ ourdb = { path = "../../herolib_rust/packages/data/ourdb" }
tst = { path = "../../herolib_rust/packages/data/tst" }
heromodels-derive = { path = "../heromodels-derive" }
heromodels_core = { path = "../heromodels_core" }
rhailib-macros = { path = "../../herolib_rust/rhailib/src/macros" }
rhai = { version = "1.21.0", features = [
"std",
"sync",
"decimal",
"internals",
] } # Added "decimal" feature, sync for Arc<Mutex<>>
rust_decimal = { version = "1.36", features = ["serde"] }
strum = "0.26"
strum_macros = "0.26"
uuid = { version = "1.17.0", features = ["v4"] }

View File

@@ -73,7 +73,7 @@ fn main() {
// The `#[model]` derive handles `created_at` and `updated_at` in `base_data`.
// `base_data.touch()` might be called internally by setters or needs explicit call if fields are set directly.
// For builder pattern, the final state of `base_data.updated_at` reflects the time of the last builder call if `touch()` is implicit.
// For builder pattern, the final state of `base_data.modified_at` reflects the time of the last builder call if `touch()` is implicit.
// If not, one might call `contract.base_data.touch()` after building.
println!("\n--- Initial Contract Details ---");

View File

@@ -0,0 +1,148 @@
use crate::db::Db;
use rhailib_macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Dynamic, Engine, EvalAltResult, Module};
use std::mem;
use std::sync::Arc;
use heromodels::models::access::Access;
type RhaiAccess = Access;
use heromodels::db::hero::OurDB;
use heromodels::db::Collection;
#[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 object_id
#[rhai_fn(name = "object_id", return_raw)]
pub fn set_object_id(
access: &mut RhaiAccess,
object_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = macros::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 circle public key
#[rhai_fn(name = "circle_public_key", return_raw)]
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 group id
#[rhai_fn(name = "group_id", return_raw)]
pub fn set_group_id(
access: &mut RhaiAccess,
group_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = macros::id_from_i64_to_u32(group_id)?;
let owned_access = std::mem::take(access);
*access = owned_access.group_id(id);
Ok(access.clone())
}
/// Sets the contact id
#[rhai_fn(name = "contact_id", return_raw)]
pub fn set_contact_id(
access: &mut RhaiAccess,
contact_id: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let id = macros::id_from_i64_to_u32(contact_id)?;
let owned_access = std::mem::take(access);
*access = owned_access.contact_id(id);
Ok(access.clone())
}
/// Sets the expiration time
#[rhai_fn(name = "expires_at", return_raw)]
pub fn set_expires_at(
access: &mut RhaiAccess,
expires_at: i64,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
let owned_access = std::mem::take(access);
*access = owned_access.expires_at(expires_at);
Ok(access.clone())
}
// Access Getters
#[rhai_fn(name = "get_access_id")]
pub fn get_access_id(access: &mut RhaiAccess) -> i64 {
access.base.id as i64
}
#[rhai_fn(name = "get_access_object_id")]
pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 {
access.object_id as i64
}
#[rhai_fn(name = "get_access_circle_pk")]
pub fn get_access_circle_pk(access: &mut RhaiAccess) -> String {
access.circle_pk.clone()
}
#[rhai_fn(name = "get_access_group_id")]
pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 {
access.group_id as i64
}
#[rhai_fn(name = "get_access_contact_id")]
pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 {
access.contact_id as i64
}
#[rhai_fn(name = "get_access_expires_at")]
pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 {
access.expires_at
}
#[rhai_fn(name = "get_access_created_at")]
pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 {
access.base.created_at
}
#[rhai_fn(name = "get_access_modified_at")]
pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 {
access.base.modified_at
}
}
pub fn register_access_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_access_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_access",
resource_type_str: "Access",
rhai_return_rust_type: heromodels::models::access::Access
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_access",
resource_type_str: "Access",
rhai_return_rust_type: heromodels::models::access::Access
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_access",
resource_type_str: "Access",
rhai_return_rust_type: heromodels::models::access::Access
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,422 @@
use heromodels::db::Db;
use macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Array, Engine, EvalAltResult, Module, Position, FLOAT, INT};
use std::mem;
use std::sync::Arc;
use heromodels::db::hero::OurDB;
use heromodels::db::Collection;
use heromodels::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType};
use heromodels::models::biz::company::{BusinessType, Company, CompanyStatus};
use heromodels::models::biz::sale::{Sale, SaleItem, SaleStatus};
use heromodels::models::biz::shareholder::{Shareholder, ShareholderType};
type RhaiProduct = Product;
type RhaiProductComponent = ProductComponent;
type RhaiCompany = Company;
type RhaiSale = Sale;
type RhaiSaleItem = SaleItem;
type RhaiShareholder = Shareholder;
#[export_module]
mod rhai_product_component_module {
use super::{RhaiProductComponent, INT};
#[rhai_fn(name = "new_product_component", return_raw)]
pub fn new_product_component() -> Result<RhaiProductComponent, Box<EvalAltResult>> {
Ok(ProductComponent::new())
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
component: &mut RhaiProductComponent,
name: String,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned = std::mem::take(component);
*component = owned.name(name);
Ok(component.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
component: &mut RhaiProductComponent,
description: String,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned = std::mem::take(component);
*component = owned.description(description);
Ok(component.clone())
}
#[rhai_fn(name = "quantity", return_raw)]
pub fn set_quantity(
component: &mut RhaiProductComponent,
quantity: INT,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned = std::mem::take(component);
*component = owned.quantity(quantity as u32);
Ok(component.clone())
}
// --- Getters ---
#[rhai_fn(name = "get_name")]
pub fn get_name(c: &mut RhaiProductComponent) -> String {
c.name.clone()
}
#[rhai_fn(name = "get_description")]
pub fn get_description(c: &mut RhaiProductComponent) -> String {
c.description.clone()
}
#[rhai_fn(name = "get_quantity")]
pub fn get_quantity(c: &mut RhaiProductComponent) -> INT {
c.quantity as INT
}
}
#[export_module]
mod rhai_product_module {
use super::{Array, ProductStatus, ProductType, RhaiProduct, RhaiProductComponent, FLOAT, INT};
#[rhai_fn(name = "new_product", return_raw)]
pub fn new_product() -> Result<RhaiProduct, Box<EvalAltResult>> {
Ok(Product::new())
}
// --- Setters ---
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
product: &mut RhaiProduct,
name: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.name(name);
Ok(product.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
product: &mut RhaiProduct,
description: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.description(description);
Ok(product.clone())
}
#[rhai_fn(name = "price", return_raw)]
pub fn set_price(
product: &mut RhaiProduct,
price: FLOAT,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.price(price);
Ok(product.clone())
}
#[rhai_fn(name = "category", return_raw)]
pub fn set_category(
product: &mut RhaiProduct,
category: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.category(category);
Ok(product.clone())
}
#[rhai_fn(name = "max_amount", return_raw)]
pub fn set_max_amount(
product: &mut RhaiProduct,
max_amount: INT,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.max_amount(max_amount as u32);
Ok(product.clone())
}
#[rhai_fn(name = "purchase_till", return_raw)]
pub fn set_purchase_till(
product: &mut RhaiProduct,
purchase_till: INT,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.purchase_till(purchase_till);
Ok(product.clone())
}
#[rhai_fn(name = "active_till", return_raw)]
pub fn set_active_till(
product: &mut RhaiProduct,
active_till: INT,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.active_till(active_till);
Ok(product.clone())
}
#[rhai_fn(name = "type", return_raw)]
pub fn set_type(
product: &mut RhaiProduct,
type_str: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let product_type = match type_str.to_lowercase().as_str() {
"physical" => ProductType::Physical,
"digital" => ProductType::Digital,
"service" => ProductType::Service,
"subscription" => ProductType::Subscription,
_ => {
return Err(EvalAltResult::ErrorSystem(
"Invalid ProductType".to_string(),
"Must be one of: Physical, Digital, Service, Subscription".into(),
)
.into())
}
};
let owned = std::mem::take(product);
*product = owned.product_type(product_type);
Ok(product.clone())
}
#[rhai_fn(name = "status", return_raw)]
pub fn set_status(
product: &mut RhaiProduct,
status_str: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let status = match status_str.to_lowercase().as_str() {
"active" => ProductStatus::Active,
"inactive" => ProductStatus::Inactive,
"discontinued" => ProductStatus::Discontinued,
_ => {
return Err(EvalAltResult::ErrorSystem(
"Invalid ProductStatus".to_string(),
"Must be one of: Active, Inactive, Discontinued".into(),
)
.into())
}
};
let owned = std::mem::take(product);
*product = owned.status(status);
Ok(product.clone())
}
#[rhai_fn(name = "add_component", return_raw)]
pub fn add_component(
product: &mut RhaiProduct,
component: RhaiProductComponent,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned = std::mem::take(product);
*product = owned.add_component(component);
Ok(product.clone())
}
#[rhai_fn(name = "set_components", return_raw)]
pub fn set_components(
product: &mut RhaiProduct,
components: Array,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let mut product_components = Vec::new();
for component_dynamic in components {
if let Ok(component) = component_dynamic.try_cast::<RhaiProductComponent>() {
product_components.push(component);
} else {
return Err(EvalAltResult::ErrorSystem(
"Invalid component type".to_string(),
"All components must be ProductComponent objects".into(),
)
.into());
}
}
let owned = std::mem::take(product);
*product = owned.components(product_components);
Ok(product.clone())
}
// --- Getters ---
#[rhai_fn(name = "get_id")]
pub fn get_id(p: &mut RhaiProduct) -> i64 {
p.base.id as i64
}
#[rhai_fn(name = "get_name")]
pub fn get_name(p: &mut RhaiProduct) -> String {
p.name.clone()
}
#[rhai_fn(name = "get_description")]
pub fn get_description(p: &mut RhaiProduct) -> String {
p.description.clone()
}
#[rhai_fn(name = "get_price")]
pub fn get_price(p: &mut RhaiProduct) -> FLOAT {
p.price
}
#[rhai_fn(name = "get_category")]
pub fn get_category(p: &mut RhaiProduct) -> String {
p.category.clone()
}
#[rhai_fn(name = "get_max_amount")]
pub fn get_max_amount(p: &mut RhaiProduct) -> INT {
p.max_amount as INT
}
#[rhai_fn(name = "get_purchase_till")]
pub fn get_purchase_till(p: &mut RhaiProduct) -> INT {
p.purchase_till
}
#[rhai_fn(name = "get_active_till")]
pub fn get_active_till(p: &mut RhaiProduct) -> INT {
p.active_till
}
#[rhai_fn(name = "get_type")]
pub fn get_type(p: &mut RhaiProduct) -> String {
format!("{:?}", p.product_type)
}
#[rhai_fn(name = "get_status")]
pub fn get_status(p: &mut RhaiProduct) -> String {
format!("{:?}", p.status)
}
#[rhai_fn(name = "get_components")]
pub fn get_components(p: &mut RhaiProduct) -> Array {
p.components
.iter()
.map(|c| rhai::Dynamic::from(c.clone()))
.collect()
}
}
pub fn register_product_rhai_module(engine: &mut Engine) {
let mut product_module = exported_module!(rhai_product_module);
let mut component_module = exported_module!(rhai_product_component_module);
register_authorized_create_by_id_fn!(
product_module: &mut product_module,
rhai_fn_name: "save_product",
resource_type_str: "Product",
rhai_return_rust_type: heromodels::models::biz::product::Product
);
register_authorized_get_by_id_fn!(
product_module: &mut product_module,
rhai_fn_name: "get_product",
resource_type_str: "Product",
rhai_return_rust_type: heromodels::models::biz::product::Product
);
register_authorized_delete_by_id_fn!(
product_module: &mut product_module,
rhai_fn_name: "delete_product",
resource_type_str: "Product",
rhai_return_rust_type: heromodels::models::biz::product::Product
);
engine.register_global_module(product_module.into());
engine.register_global_module(component_module.into());
}
// Company Rhai wrapper functions
#[export_module]
mod rhai_company_module {
use super::{BusinessType, CompanyStatus, RhaiCompany};
#[rhai_fn(name = "new_company", return_raw)]
pub fn new_company() -> Result<RhaiCompany, Box<EvalAltResult>> {
Ok(Company::new())
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
company: &mut RhaiCompany,
name: String,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned = std::mem::take(company);
*company = owned.name(name);
Ok(company.clone())
}
#[rhai_fn(name = "get_company_id")]
pub fn get_company_id(company: &mut RhaiCompany) -> i64 {
company.id() as i64
}
#[rhai_fn(name = "get_company_name")]
pub fn get_company_name(company: &mut RhaiCompany) -> String {
company.name().clone()
}
}
pub fn register_company_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_company_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_company",
resource_type_str: "Company",
rhai_return_rust_type: heromodels::models::biz::company::Company
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_company",
resource_type_str: "Company",
rhai_return_rust_type: heromodels::models::biz::company::Company
);
engine.register_global_module(module.into());
}
// Sale Rhai wrapper functions
#[export_module]
mod rhai_sale_module {
use super::{RhaiSale, RhaiSaleItem, SaleStatus};
#[rhai_fn(name = "new_sale", return_raw)]
pub fn new_sale() -> Result<RhaiSale, Box<EvalAltResult>> {
Ok(Sale::new())
}
#[rhai_fn(name = "new_sale_item", return_raw)]
pub fn new_sale_item() -> Result<RhaiSaleItem, Box<EvalAltResult>> {
Ok(SaleItem::new())
}
#[rhai_fn(name = "company_id", return_raw)]
pub fn set_sale_company_id(sale: &mut RhaiSale, company_id: i64) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned = std::mem::take(sale);
*sale = owned.company_id(company_id as u32);
Ok(sale.clone())
}
#[rhai_fn(name = "total_amount", return_raw)]
pub fn set_sale_total_amount(sale: &mut RhaiSale, total_amount: f64) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned = std::mem::take(sale);
*sale = owned.total_amount(total_amount);
Ok(sale.clone())
}
#[rhai_fn(name = "get_sale_id")]
pub fn get_sale_id(sale: &mut RhaiSale) -> i64 {
sale.id() as i64
}
#[rhai_fn(name = "get_sale_total_amount")]
pub fn get_sale_total_amount(sale: &mut RhaiSale) -> f64 {
sale.total_amount()
}
}
pub fn register_sale_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_sale_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_sale",
resource_type_str: "Sale",
rhai_return_rust_type: heromodels::models::biz::sale::Sale
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_sale",
resource_type_str: "Sale",
rhai_return_rust_type: heromodels::models::biz::sale::Sale
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,246 @@
use crate::db::Db;
use rhailib_macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, Module};
use std::mem;
use std::sync::Arc;
use crate::models::calendar::{AttendanceStatus, Attendee, Calendar, Event};
type RhaiCalendar = Calendar;
type RhaiEvent = Event;
type RhaiAttendee = Attendee;
use crate::db::hero::OurDB;
use crate::db::Collection;
#[export_module]
mod rhai_calendar_module {
use super::{AttendanceStatus, RhaiAttendee, RhaiCalendar, RhaiEvent};
// --- Attendee Builder ---
#[rhai_fn(name = "new_attendee", return_raw)]
pub fn new_attendee(contact_id: i64) -> Result<RhaiAttendee, Box<EvalAltResult>> {
Ok(Attendee::new(contact_id as u32))
}
#[rhai_fn(name = "status", return_raw)]
pub fn set_attendee_status(
attendee: &mut RhaiAttendee,
status_str: String,
) -> Result<RhaiAttendee, Box<EvalAltResult>> {
let status = match status_str.to_lowercase().as_str() {
"accepted" => AttendanceStatus::Accepted,
"declined" => AttendanceStatus::Declined,
"tentative" => AttendanceStatus::Tentative,
"noresponse" => AttendanceStatus::NoResponse,
_ => {
return Err(EvalAltResult::ErrorSystem(
"Invalid Status".to_string(),
"Must be one of: Accepted, Declined, Tentative, NoResponse".into(),
)
.into())
}
};
let owned = std::mem::take(attendee);
*attendee = owned.status(status);
Ok(attendee.clone())
}
// --- Event Builder ---
#[rhai_fn(name = "new_event", return_raw)]
pub fn new_event() -> Result<RhaiEvent, Box<EvalAltResult>> {
Ok(Event::new())
}
#[rhai_fn(name = "title", return_raw)]
pub fn set_event_title(
event: &mut RhaiEvent,
title: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned = std::mem::take(event);
*event = owned.title(title);
Ok(event.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_event_description(
event: &mut RhaiEvent,
description: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned = std::mem::take(event);
*event = owned.description(description);
Ok(event.clone())
}
#[rhai_fn(name = "location", return_raw)]
pub fn set_event_location(
event: &mut RhaiEvent,
location: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned = std::mem::take(event);
*event = owned.location(location);
Ok(event.clone())
}
#[rhai_fn(name = "add_attendee", return_raw)]
pub fn add_event_attendee(
event: &mut RhaiEvent,
attendee: RhaiAttendee,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned = std::mem::take(event);
*event = owned.add_attendee(attendee);
Ok(event.clone())
}
#[rhai_fn(name = "reschedule", return_raw)]
pub fn reschedule_event(
event: &mut RhaiEvent,
start_time: i64,
end_time: i64,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned = std::mem::take(event);
*event = owned.reschedule(start_time, end_time);
Ok(event.clone())
}
// --- Calendar Builder ---
#[rhai_fn(name = "new_calendar", return_raw)]
pub fn new_calendar(name: String) -> Result<RhaiCalendar, Box<EvalAltResult>> {
Ok(Calendar::new().name(name))
}
#[rhai_fn(name = "calendar_name", return_raw)]
pub fn set_calendar_name(
calendar: &mut RhaiCalendar,
name: String,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
let owned = std::mem::take(calendar);
*calendar = owned.name(name);
Ok(calendar.clone())
}
#[rhai_fn(name = "calendar_description", return_raw)]
pub fn set_calendar_description(
calendar: &mut RhaiCalendar,
description: String,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
let owned = std::mem::take(calendar);
*calendar = owned.description(description);
Ok(calendar.clone())
}
#[rhai_fn(name = "add_event", return_raw)]
pub fn add_calendar_event(
calendar: &mut RhaiCalendar,
event_id: i64,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
let owned = std::mem::take(calendar);
*calendar = owned.add_event(event_id as u32);
Ok(calendar.clone())
}
// --- Getters ---
// Calendar
#[rhai_fn(name = "get_calendar_id")]
pub fn get_calendar_id(c: &mut RhaiCalendar) -> i64 {
c.base.id as i64
}
#[rhai_fn(name = "get_calendar_name")]
pub fn get_calendar_name(c: &mut RhaiCalendar) -> String {
c.name.clone()
}
#[rhai_fn(name = "get_calendar_description")]
pub fn get_calendar_description(c: &mut RhaiCalendar) -> Option<String> {
c.description.clone()
}
#[rhai_fn(name = "get_calendar_events")]
pub fn get_calendar_events(c: &mut RhaiCalendar) -> Array {
c.events.iter().map(|id| Dynamic::from(*id as i64)).collect()
}
// Event
#[rhai_fn(name = "get_event_id")]
pub fn get_event_id(e: &mut RhaiEvent) -> i64 {
e.base.id as i64
}
#[rhai_fn(name = "get_event_title")]
pub fn get_event_title(e: &mut RhaiEvent) -> String {
e.title.clone()
}
#[rhai_fn(name = "get_event_description")]
pub fn get_event_description(e: &mut RhaiEvent) -> Option<String> {
e.description.clone()
}
#[rhai_fn(name = "get_event_start_time")]
pub fn get_event_start_time(e: &mut RhaiEvent) -> i64 {
e.start_time
}
#[rhai_fn(name = "get_event_end_time")]
pub fn get_event_end_time(e: &mut RhaiEvent) -> i64 {
e.end_time
}
#[rhai_fn(name = "get_event_attendees")]
pub fn get_event_attendees(e: &mut RhaiEvent) -> Array {
e.attendees.iter().map(|a| Dynamic::from(a.clone())).collect()
}
#[rhai_fn(name = "get_event_location")]
pub fn get_event_location(e: &mut RhaiEvent) -> Option<String> {
e.location.clone()
}
// Attendee
#[rhai_fn(name = "get_attendee_contact_id")]
pub fn get_attendee_contact_id(a: &mut RhaiAttendee) -> i64 {
a.contact_id as i64
}
#[rhai_fn(name = "get_attendee_status")]
pub fn get_attendee_status(a: &mut RhaiAttendee) -> String {
format!("{:?}", a.status)
}
}
pub fn register_calendar_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_calendar_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_calendar",
resource_type_str: "Calendar",
rhai_return_rust_type: heromodels::models::calendar::Calendar
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_calendar",
resource_type_str: "Calendar",
rhai_return_rust_type: heromodels::models::calendar::Calendar
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_calendar",
resource_type_str: "Calendar",
rhai_return_rust_type: heromodels::models::calendar::Calendar
);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_event",
resource_type_str: "Event",
rhai_return_rust_type: heromodels::models::calendar::Event
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_event",
resource_type_str: "Event",
rhai_return_rust_type: heromodels::models::calendar::Event
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_event",
resource_type_str: "Event",
rhai_return_rust_type: heromodels::models::calendar::Event
);
engine.register_global_module(module.into());
}

View File

@@ -1,412 +1,155 @@
use crate::db::Db;
use rhailib_macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn, register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map, Module};
use std::collections::HashMap;
use std::sync::Arc;
use super::circle::{Circle, ThemeData};
use crate::models::circle::Circle;
type RhaiCircle = Circle;
type RhaiThemeData = ThemeData;
use crate::db::Collection;
use crate::db::hero::OurDB;
use serde::Serialize;
use serde_json;
/// Registers a `.json()` method for any type `T` that implements the required traits.
fn register_json_method<T>(engine: &mut Engine)
where
T: CustomType + Clone + Serialize,
{
let to_json_fn = |obj: &mut T| -> Result<String, Box<EvalAltResult>> {
serde_json::to_string(obj).map_err(|e| e.to_string().into())
};
engine.build_type::<T>().register_fn("json", to_json_fn);
}
// 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_theme_data_module {
#[rhai_fn(name = "new_theme_data")]
pub fn new_theme_data() -> RhaiThemeData {
ThemeData::default()
}
// --- Setters for ThemeData ---
#[rhai_fn(name = "primary_color", return_raw, global, pure)]
pub fn set_primary_color(
theme: &mut RhaiThemeData,
color: String,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.primary_color = color;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "background_color", return_raw, global, pure)]
pub fn set_background_color(
theme: &mut RhaiThemeData,
color: String,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.background_color = color;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "background_pattern", return_raw, global, pure)]
pub fn set_background_pattern(
theme: &mut RhaiThemeData,
pattern: String,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.background_pattern = pattern;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "logo_symbol", return_raw, global, pure)]
pub fn set_logo_symbol(
theme: &mut RhaiThemeData,
symbol: String,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.logo_symbol = symbol;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "logo_url", return_raw, global, pure)]
pub fn set_logo_url(
theme: &mut RhaiThemeData,
url: String,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.logo_url = url;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "nav_dashboard_visible", return_raw, global, pure)]
pub fn set_nav_dashboard_visible(
theme: &mut RhaiThemeData,
visible: bool,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.nav_dashboard_visible = visible;
*theme = owned_theme;
Ok(theme.clone())
}
#[rhai_fn(name = "nav_timeline_visible", return_raw, global, pure)]
pub fn set_nav_timeline_visible(
theme: &mut RhaiThemeData,
visible: bool,
) -> Result<RhaiThemeData, Box<EvalAltResult>> {
let mut owned_theme = mem::take(theme);
owned_theme.nav_timeline_visible = visible;
*theme = owned_theme;
Ok(theme.clone())
}
// --- Getters for ThemeData ---
#[rhai_fn(name = "get_primary_color", pure)]
pub fn get_primary_color(theme: &mut RhaiThemeData) -> String {
theme.primary_color.clone()
}
#[rhai_fn(name = "get_background_color", pure)]
pub fn get_background_color(theme: &mut RhaiThemeData) -> String {
theme.background_color.clone()
}
#[rhai_fn(name = "get_background_pattern", pure)]
pub fn get_background_pattern(theme: &mut RhaiThemeData) -> String {
theme.background_pattern.clone()
}
#[rhai_fn(name = "get_logo_symbol", pure)]
pub fn get_logo_symbol(theme: &mut RhaiThemeData) -> String {
theme.logo_symbol.clone()
}
#[rhai_fn(name = "get_logo_url", pure)]
pub fn get_logo_url(theme: &mut RhaiThemeData) -> String {
theme.logo_url.clone()
}
#[rhai_fn(name = "get_nav_dashboard_visible", pure)]
pub fn get_nav_dashboard_visible(theme: &mut RhaiThemeData) -> bool {
theme.nav_dashboard_visible
}
#[rhai_fn(name = "get_nav_timeline_visible", pure)]
pub fn get_nav_timeline_visible(theme: &mut RhaiThemeData) -> bool {
theme.nav_timeline_visible
}
}
use crate::db::Collection;
use crate::models::circle::ThemeData;
#[export_module]
mod rhai_circle_module {
// --- Circle Functions ---
#[rhai_fn(name = "new_circle")]
pub fn new_circle() -> RhaiCircle {
Circle::new()
use super::RhaiCircle;
// this one configures the users own circle
#[rhai_fn(name = "configure", return_raw)]
pub fn configure() -> Result<RhaiCircle, Box<EvalAltResult>> {
Ok(Circle::new())
}
/// Sets the circle title
#[rhai_fn(name = "title", return_raw, global, pure)]
pub fn circle_title(
#[rhai_fn(name = "new_circle", return_raw)]
pub fn new_circle() -> Result<RhaiCircle, Box<EvalAltResult>> {
Ok(Circle::new())
}
#[rhai_fn(name = "set_title", return_raw)]
pub fn set_title(
circle: &mut RhaiCircle,
title: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.title(title);
let owned = std::mem::take(circle);
*circle = owned.title(title);
Ok(circle.clone())
}
/// Sets the circle ws_url
#[rhai_fn(name = "ws_url", return_raw, global, pure)]
pub fn circle_ws_url(
#[rhai_fn(name = "set_ws_url", return_raw)]
pub fn set_ws_url(
circle: &mut RhaiCircle,
ws_url: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.ws_url(ws_url);
let owned = std::mem::take(circle);
*circle = owned.ws_url(ws_url);
Ok(circle.clone())
}
/// Sets the circle description
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn circle_description(
#[rhai_fn(name = "set_description", return_raw)]
pub fn set_description(
circle: &mut RhaiCircle,
description: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.description(description);
let owned = std::mem::take(circle);
*circle = owned.description(description);
Ok(circle.clone())
}
/// Sets the circle logo
#[rhai_fn(name = "logo", return_raw, global, pure)]
pub fn circle_logo(
#[rhai_fn(name = "set_logo", return_raw)]
pub fn set_logo(
circle: &mut RhaiCircle,
logo: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.logo(logo);
let owned = std::mem::take(circle);
*circle = owned.logo(logo);
Ok(circle.clone())
}
/// Sets the circle theme
#[rhai_fn(name = "theme", return_raw, global, pure)]
pub fn circle_theme(
#[rhai_fn(name = "set_theme", return_raw)]
pub fn set_theme(
circle: &mut RhaiCircle,
theme: RhaiThemeData,
theme: ThemeData,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.theme(theme);
let owned = std::mem::take(circle);
*circle = owned.theme(theme);
Ok(circle.clone())
}
/// Adds an attendee to the circle
#[rhai_fn(name = "add_circle", return_raw, global, pure)]
pub fn circle_add_circle(
#[rhai_fn(name = "add_circle", return_raw)]
pub fn add_circle(
circle: &mut RhaiCircle,
added_circle: String,
new_circle: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.add_circle(added_circle);
let owned = std::mem::take(circle);
*circle = owned.add_circle(new_circle);
Ok(circle.clone())
}
/// Adds an attendee to the circle
#[rhai_fn(name = "add_member", return_raw, global, pure)]
pub fn circle_add_member(
#[rhai_fn(name = "add_member", return_raw)]
pub fn add_member(
circle: &mut RhaiCircle,
added_member: String,
member: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle);
*circle = owned_circle.add_member(added_member);
let owned = std::mem::take(circle);
*circle = owned.add_member(member);
Ok(circle.clone())
}
// Circle Getters
#[rhai_fn(name = "get_id", pure)]
pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 {
circle.base_data.id as i64
// --- Getters ---
#[rhai_fn(name = "get_id")]
pub fn get_id(c: &mut RhaiCircle) -> i64 {
c.base_data.id as i64
}
#[rhai_fn(name = "get_created_at", pure)]
pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 {
circle.base_data.created_at
#[rhai_fn(name = "get_title")]
pub fn get_title(c: &mut RhaiCircle) -> String {
c.title.clone()
}
#[rhai_fn(name = "get_modified_at", pure)]
pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 {
circle.base_data.modified_at
#[rhai_fn(name = "get_ws_url")]
pub fn get_ws_url(c: &mut RhaiCircle) -> String {
c.ws_url.clone()
}
#[rhai_fn(name = "get_title", pure)]
pub fn get_circle_title(circle: &mut RhaiCircle) -> String {
circle.title.clone()
#[rhai_fn(name = "get_description")]
pub fn get_description(c: &mut RhaiCircle) -> Option<String> {
c.description.clone()
}
#[rhai_fn(name = "get_description", pure)]
pub fn get_circle_description(circle: &mut RhaiCircle) -> Option<String> {
circle.description.clone()
#[rhai_fn(name = "get_logo")]
pub fn get_logo(c: &mut RhaiCircle) -> Option<String> {
c.logo.clone()
}
#[rhai_fn(name = "get_circles", pure)]
pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec<String> {
circle.circles.clone()
#[rhai_fn(name = "get_circles")]
pub fn get_circles(c: &mut RhaiCircle) -> Array {
c.circles.iter().map(|s| Dynamic::from(s.clone())).collect()
}
#[rhai_fn(name = "get_ws_url", pure)]
pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String {
circle.ws_url.clone()
}
#[rhai_fn(name = "get_logo", pure)]
pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option<String> {
circle.logo.clone()
}
#[rhai_fn(name = "get_theme", pure)]
pub fn get_circle_theme(circle: &mut RhaiCircle) -> RhaiThemeData {
circle.theme.clone()
#[rhai_fn(name = "get_members")]
pub fn get_members(c: &mut RhaiCircle) -> Array {
c.members.iter().map(|s| Dynamic::from(s.clone())).collect()
}
}
pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
engine.build_type::<RhaiCircle>();
engine.build_type::<RhaiThemeData>();
pub fn register_circle_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_circle_module);
let mut db_module = Module::new();
let circle_module = exported_module!(rhai_circle_module);
let theme_data_module = exported_module!(rhai_theme_data_module);
engine.register_global_module(circle_module.into());
engine.register_global_module(theme_data_module.into());
register_json_method::<Circle>(engine);
register_json_method::<ThemeData>(engine);
// Manually register database functions as they need to capture 'db'
let db_clone_set_circle = db.clone();
db_module.set_native_fn(
"save_circle",
move |circle: Circle| -> Result<Circle, Box<EvalAltResult>> {
let result = db_clone_set_circle.set(&circle).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_circle: {}", e).into(),
Position::NONE,
))
})?;
Ok(result.1)
},
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_circle",
resource_type_str: "Circle",
rhai_return_rust_type: crate::models::circle::Circle
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_circle",
resource_type_str: "Circle",
rhai_return_rust_type: crate::models::circle::Circle
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_circle",
resource_type_str: "Circle",
rhai_return_rust_type: crate::models::circle::Circle
);
let db_clone_delete_circle = db.clone();
db_module.set_native_fn(
"delete_circle",
move |circle: Circle| -> Result<(), Box<EvalAltResult>> {
let result = db_clone_delete_circle
.collection::<Circle>()
.expect("can open circle collection")
.delete_by_id(circle.base_data.id)
.expect("can delete circle");
Ok(result)
},
);
let db_clone_get_circle = db.clone();
db_module.set_native_fn(
"get_circle",
move || -> Result<Circle, Box<EvalAltResult>> {
let all_circles: Vec<Circle> = db_clone_get_circle.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_circle: {}", e).into(),
Position::NONE,
))
})?;
if let Some(first_circle) = all_circles.first() {
Ok(first_circle.clone())
} else {
Err(Box::new(EvalAltResult::ErrorRuntime(
"Circle not found".into(),
Position::NONE,
)))
}
},
);
// --- Collection DB Functions ---
let db_clone = db.clone();
db_module.set_native_fn(
"save_circle",
move |circle: RhaiCircle| -> Result<RhaiCircle, Box<EvalAltResult>> {
let result = db_clone.set(&circle).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error: {:?}", e).into(),
Position::NONE,
))
})?;
Ok(result.1)
},
);
let db_clone_get_circle_by_id = db.clone();
db_module.set_native_fn(
"get_circle_by_id",
move |id_i64: INT| -> Result<Circle, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?;
db_clone_get_circle_by_id
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_circle_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Circle with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone_list_circles = db.clone();
db_module.set_native_fn(
"list_circles",
move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_circles.collection::<Circle>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get circle collection: {:?}", e).into(),
Position::NONE,
))
})?;
let circles = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all circles: {:?}", e).into(),
Position::NONE,
))
})?;
let mut array = Array::new();
for circle in circles {
array.push(Dynamic::from(circle));
}
Ok(Dynamic::from(array))
},
);
engine.register_global_module(db_module.into());
println!("Successfully registered circle Rhai module using export_module approach.");
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,232 @@
use crate::db::Db;
use rhailib_macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, Module};
use std::mem;
use std::sync::Arc;
use crate::models::contact::{Contact, Group};
type RhaiContact = Contact;
type RhaiGroup = Group;
use crate::db::hero::OurDB;
use crate::db::Collection;
#[export_module]
mod rhai_contact_module {
use super::{RhaiContact, RhaiGroup};
// --- Contact Builder ---
#[rhai_fn(name = "new_contact", return_raw)]
pub fn new_contact() -> Result<RhaiContact, Box<EvalAltResult>> {
Ok(Contact::new())
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_contact_name(
contact: &mut RhaiContact,
name: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.name(name);
Ok(contact.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_contact_description(
contact: &mut RhaiContact,
description: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.description(description);
Ok(contact.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_contact_address(
contact: &mut RhaiContact,
address: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.address(address);
Ok(contact.clone())
}
#[rhai_fn(name = "phone", return_raw)]
pub fn set_contact_phone(
contact: &mut RhaiContact,
phone: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.phone(phone);
Ok(contact.clone())
}
#[rhai_fn(name = "email", return_raw)]
pub fn set_contact_email(
contact: &mut RhaiContact,
email: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.email(email);
Ok(contact.clone())
}
#[rhai_fn(name = "notes", return_raw)]
pub fn set_contact_notes(
contact: &mut RhaiContact,
notes: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.notes(notes);
Ok(contact.clone())
}
#[rhai_fn(name = "circle", return_raw)]
pub fn set_contact_circle(
contact: &mut RhaiContact,
circle: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
let owned = std::mem::take(contact);
*contact = owned.circle(circle);
Ok(contact.clone())
}
// --- Group Builder ---
#[rhai_fn(name = "new_group", return_raw)]
pub fn new_group() -> Result<RhaiGroup, Box<EvalAltResult>> {
Ok(Group::new())
}
#[rhai_fn(name = "group_name", return_raw)]
pub fn set_group_name(
group: &mut RhaiGroup,
name: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned = std::mem::take(group);
*group = owned.name(name);
Ok(group.clone())
}
#[rhai_fn(name = "group_description", return_raw)]
pub fn set_group_description(
group: &mut RhaiGroup,
description: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned = std::mem::take(group);
*group = owned.description(description);
Ok(group.clone())
}
#[rhai_fn(name = "add_contact", return_raw)]
pub fn add_group_contact(
group: &mut RhaiGroup,
contact_id: i64,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned = std::mem::take(group);
*group = owned.add_contact(contact_id as u32);
Ok(group.clone())
}
// --- Getters ---
// Contact
#[rhai_fn(name = "get_contact_id")]
pub fn get_contact_id(c: &mut RhaiContact) -> i64 {
c.base.id as i64
}
#[rhai_fn(name = "get_contact_name")]
pub fn get_contact_name(c: &mut RhaiContact) -> String {
c.name.clone()
}
#[rhai_fn(name = "get_contact_description")]
pub fn get_contact_description(c: &mut RhaiContact) -> Option<String> {
c.description.clone()
}
#[rhai_fn(name = "get_contact_address")]
pub fn get_contact_address(c: &mut RhaiContact) -> String {
c.address.clone()
}
#[rhai_fn(name = "get_contact_phone")]
pub fn get_contact_phone(c: &mut RhaiContact) -> String {
c.phone.clone()
}
#[rhai_fn(name = "get_contact_email")]
pub fn get_contact_email(c: &mut RhaiContact) -> String {
c.email.clone()
}
#[rhai_fn(name = "get_contact_notes")]
pub fn get_contact_notes(c: &mut RhaiContact) -> Option<String> {
c.notes.clone()
}
#[rhai_fn(name = "get_contact_circle")]
pub fn get_contact_circle(c: &mut RhaiContact) -> String {
c.circle.clone()
}
// Group
#[rhai_fn(name = "get_group_id")]
pub fn get_group_id(g: &mut RhaiGroup) -> i64 {
g.base.id as i64
}
#[rhai_fn(name = "get_group_name")]
pub fn get_group_name(g: &mut RhaiGroup) -> String {
g.name.clone()
}
#[rhai_fn(name = "get_group_description")]
pub fn get_group_description(g: &mut RhaiGroup) -> Option<String> {
g.description.clone()
}
#[rhai_fn(name = "get_group_contacts")]
pub fn get_group_contacts(g: &mut RhaiGroup) -> Array {
g.contacts
.iter()
.map(|id| Dynamic::from(*id as i64))
.collect()
}
}
pub fn register_contact_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_contact_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_contact",
resource_type_str: "Contact",
rhai_return_rust_type: heromodels::models::contact::Contact
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_contact",
resource_type_str: "Contact",
rhai_return_rust_type: heromodels::models::contact::Contact
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_contact",
resource_type_str: "Contact",
rhai_return_rust_type: heromodels::models::contact::Contact
);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_group",
resource_type_str: "Group",
rhai_return_rust_type: heromodels::models::contact::Group
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_group",
resource_type_str: "Group",
rhai_return_rust_type: heromodels::models::contact::Group
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_group",
resource_type_str: "Group",
rhai_return_rust_type: heromodels::models::contact::Group
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,86 @@
use heromodels::db::Db;
use macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Engine, EvalAltResult, Module, INT};
use std::mem;
use std::sync::Arc;
use heromodels::models::core::comment::Comment;
type RhaiComment = Comment;
use heromodels::db::hero::OurDB;
use heromodels::db::Collection;
#[export_module]
mod rhai_comment_module {
use super::{RhaiComment, INT};
#[rhai_fn(name = "new_comment", return_raw)]
pub fn new_comment() -> Result<RhaiComment, Box<EvalAltResult>> {
Ok(Comment::new())
}
#[rhai_fn(name = "user_id", return_raw)]
pub fn set_user_id(
comment: &mut RhaiComment,
user_id: i64,
) -> Result<RhaiComment, Box<EvalAltResult>> {
let owned = std::mem::take(comment);
*comment = owned.user_id(user_id as u32);
Ok(comment.clone())
}
#[rhai_fn(name = "content", return_raw)]
pub fn set_content(
comment: &mut RhaiComment,
content: String,
) -> Result<RhaiComment, Box<EvalAltResult>> {
let owned = std::mem::take(comment);
*comment = owned.content(content);
Ok(comment.clone())
}
#[rhai_fn(name = "get_comment_id")]
pub fn get_comment_id(comment: &mut RhaiComment) -> i64 {
comment.id() as i64
}
#[rhai_fn(name = "get_comment_user_id")]
pub fn get_comment_user_id(comment: &mut RhaiComment) -> i64 {
comment.user_id() as i64
}
#[rhai_fn(name = "get_comment_content")]
pub fn get_comment_content(comment: &mut RhaiComment) -> String {
comment.content().clone()
}
}
pub fn register_comment_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_comment_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_comment",
resource_type_str: "Comment",
rhai_return_rust_type: heromodels::models::core::comment::Comment
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_comment",
resource_type_str: "Comment",
rhai_return_rust_type: heromodels::models::core::comment::Comment
);
register_authorized_delete_by_id_fn!(
module: &mut module,
rhai_fn_name: "delete_comment",
resource_type_str: "Comment",
rhai_return_rust_type: heromodels::models::core::comment::Comment
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,80 @@
use heromodels::db::Db;
use macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn,
};
use rhai::plugin::*;
use rhai::{Array, Engine, EvalAltResult, Module, INT};
use std::mem;
use std::sync::Arc;
use heromodels::db::hero::OurDB;
use heromodels::db::Collection;
use heromodels::models::finance::account::Account;
type RhaiAccount = Account;
#[export_module]
mod rhai_account_module {
use super::{Array, RhaiAccount, INT};
#[rhai_fn(name = "new_account", return_raw)]
pub fn new_account() -> Result<RhaiAccount, Box<EvalAltResult>> {
Ok(Account::new())
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
account: &mut RhaiAccount,
name: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.name(name);
Ok(account.clone())
}
#[rhai_fn(name = "user_id", return_raw)]
pub fn set_user_id(
account: &mut RhaiAccount,
user_id: INT,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.user_id(user_id as u32);
Ok(account.clone())
}
#[rhai_fn(name = "get_account_id")]
pub fn get_account_id(account: &mut RhaiAccount) -> i64 {
account.id() as i64
}
#[rhai_fn(name = "get_account_name")]
pub fn get_account_name(account: &mut RhaiAccount) -> String {
account.name().clone()
}
#[rhai_fn(name = "get_account_user_id")]
pub fn get_account_user_id(account: &mut RhaiAccount) -> INT {
account.user_id() as INT
}
}
pub fn register_account_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_account_module);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_account",
resource_type_str: "Account",
rhai_return_rust_type: heromodels::models::finance::account::Account
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_account",
resource_type_str: "Account",
rhai_return_rust_type: heromodels::models::finance::account::Account
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,16 @@
pub mod node;
pub use node::{
Node,
DeviceInfo,
StorageDevice,
MemoryDevice,
CPUDevice,
GPUDevice,
NetworkDevice,
NodeCapacity,
ComputeSlice,
StorageSlice,
PricingPolicy,
SLAPolicy,
};

View File

@@ -0,0 +1,265 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::CustomType;
use serde::{Deserialize, Serialize};
/// Storage device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct StorageDevice {
/// can be used in node
pub id: String,
/// Size of the storage device in gigabytes
pub size_gb: f64,
/// Description of the storage device
pub description: String,
}
/// Memory device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct MemoryDevice {
/// can be used in node
pub id: String,
/// Size of the memory device in gigabytes
pub size_gb: f64,
/// Description of the memory device
pub description: String,
}
/// CPU device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct CPUDevice {
/// can be used in node
pub id: String,
/// Number of CPU cores
pub cores: i32,
/// Passmark score
pub passmark: i32,
/// Description of the CPU
pub description: String,
/// Brand of the CPU
pub cpu_brand: String,
/// Version of the CPU
pub cpu_version: String,
}
/// GPU device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct GPUDevice {
/// can be used in node
pub id: String,
/// Number of GPU cores
pub cores: i32,
/// Size of the GPU memory in gigabytes
pub memory_gb: f64,
/// Description of the GPU
pub description: String,
pub gpu_brand: String,
pub gpu_version: String,
}
/// Network device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct NetworkDevice {
/// can be used in node
pub id: String,
/// Network speed in Mbps
pub speed_mbps: i32,
/// Description of the network device
pub description: String,
}
/// Aggregated device info for a node
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct DeviceInfo {
pub vendor: String,
pub storage: Vec<StorageDevice>,
pub memory: Vec<MemoryDevice>,
pub cpu: Vec<CPUDevice>,
pub gpu: Vec<GPUDevice>,
pub network: Vec<NetworkDevice>,
}
/// NodeCapacity represents the hardware capacity details of a node.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct NodeCapacity {
/// Total storage in gigabytes
pub storage_gb: f64,
/// Total memory in gigabytes
pub mem_gb: f64,
/// Total GPU memory in gigabytes
pub mem_gb_gpu: f64,
/// Passmark score for the node
pub passmark: i32,
/// Total virtual cores
pub vcores: i32,
}
/// Pricing policy for slices (minimal version until full spec available)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct PricingPolicy {
/// Human friendly policy name (e.g. "fixed", "market")
pub name: String,
/// Optional free-form details as JSON-encoded string
pub details: Option<String>,
}
/// SLA policy for slices (minimal version until full spec available)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct SLAPolicy {
/// Uptime in percentage (0..100)
pub uptime: f32,
/// Max response time in ms
pub max_response_time_ms: u32,
}
/// Compute slice (typically represents a base unit of compute)
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct ComputeSlice {
pub base_data: BaseModelData,
/// the node in the grid, there is an object describing the node
#[index]
pub nodeid: u32,
/// the id of the slice in the node
#[index]
pub id: i32,
pub mem_gb: f64,
pub storage_gb: f64,
pub passmark: i32,
pub vcores: i32,
pub cpu_oversubscription: i32,
pub storage_oversubscription: i32,
/// Min/max allowed price range for validation
#[serde(default)]
pub price_range: Vec<f64>,
/// nr of GPU's see node to know what GPU's are
pub gpus: u8,
/// price per slice (even if the grouped one)
pub price_cc: f64,
pub pricing_policy: PricingPolicy,
pub sla_policy: SLAPolicy,
}
impl ComputeSlice {
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(),
nodeid: 0,
id: 0,
mem_gb: 0.0,
storage_gb: 0.0,
passmark: 0,
vcores: 0,
cpu_oversubscription: 0,
storage_oversubscription: 0,
price_range: vec![0.0, 0.0],
gpus: 0,
price_cc: 0.0,
pricing_policy: PricingPolicy::default(),
sla_policy: SLAPolicy::default(),
}
}
pub fn nodeid(mut self, nodeid: u32) -> Self { self.nodeid = nodeid; self }
pub fn slice_id(mut self, id: i32) -> Self { self.id = id; self }
pub fn mem_gb(mut self, v: f64) -> Self { self.mem_gb = v; self }
pub fn storage_gb(mut self, v: f64) -> Self { self.storage_gb = v; self }
pub fn passmark(mut self, v: i32) -> Self { self.passmark = v; self }
pub fn vcores(mut self, v: i32) -> Self { self.vcores = v; self }
pub fn cpu_oversubscription(mut self, v: i32) -> Self { self.cpu_oversubscription = v; self }
pub fn storage_oversubscription(mut self, v: i32) -> Self { self.storage_oversubscription = v; self }
pub fn price_range(mut self, min_max: Vec<f64>) -> Self { self.price_range = min_max; self }
pub fn gpus(mut self, v: u8) -> Self { self.gpus = v; self }
pub fn price_cc(mut self, v: f64) -> Self { self.price_cc = v; self }
pub fn pricing_policy(mut self, p: PricingPolicy) -> Self { self.pricing_policy = p; self }
pub fn sla_policy(mut self, p: SLAPolicy) -> Self { self.sla_policy = p; self }
}
/// Storage slice (typically 1GB of storage)
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct StorageSlice {
pub base_data: BaseModelData,
/// the node in the grid
#[index]
pub nodeid: u32,
/// the id of the slice in the node, are tracked in the node itself
#[index]
pub id: i32,
/// price per slice (even if the grouped one)
pub price_cc: f64,
pub pricing_policy: PricingPolicy,
pub sla_policy: SLAPolicy,
}
impl StorageSlice {
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(),
nodeid: 0,
id: 0,
price_cc: 0.0,
pricing_policy: PricingPolicy::default(),
sla_policy: SLAPolicy::default(),
}
}
pub fn nodeid(mut self, nodeid: u32) -> Self { self.nodeid = nodeid; self }
pub fn slice_id(mut self, id: i32) -> Self { self.id = id; self }
pub fn price_cc(mut self, v: f64) -> Self { self.price_cc = v; self }
pub fn pricing_policy(mut self, p: PricingPolicy) -> Self { self.pricing_policy = p; self }
pub fn sla_policy(mut self, p: SLAPolicy) -> Self { self.sla_policy = p; self }
}
/// Grid4 Node model
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)]
pub struct Node {
pub base_data: BaseModelData,
/// Link to node group
#[index]
pub nodegroupid: i32,
/// Uptime percentage 0..100
pub uptime: i32,
pub computeslices: Vec<ComputeSlice>,
pub storageslices: Vec<StorageSlice>,
pub devices: DeviceInfo,
/// 2 letter code
#[index]
pub country: String,
/// Hardware capacity details
pub capacity: NodeCapacity,
/// lets keep it simple and compatible
pub provisiontime: u32,
}
impl Node {
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(),
nodegroupid: 0,
uptime: 0,
computeslices: Vec::new(),
storageslices: Vec::new(),
devices: DeviceInfo::default(),
country: String::new(),
capacity: NodeCapacity::default(),
provisiontime: 0,
}
}
pub fn nodegroupid(mut self, v: i32) -> Self { self.nodegroupid = v; self }
pub fn uptime(mut self, v: i32) -> Self { self.uptime = v; self }
pub fn add_compute_slice(mut self, s: ComputeSlice) -> Self { self.computeslices.push(s); self }
pub fn add_storage_slice(mut self, s: StorageSlice) -> Self { self.storageslices.push(s); self }
pub fn devices(mut self, d: DeviceInfo) -> Self { self.devices = d; self }
pub fn country(mut self, c: impl ToString) -> Self { self.country = c.to_string(); self }
pub fn capacity(mut self, c: NodeCapacity) -> Self { self.capacity = c; self }
pub fn provisiontime(mut self, t: u32) -> Self { self.provisiontime = t; self }
/// Placeholder for capacity recalculation out of the devices on the Node
pub fn recalc_capacity(mut self) -> Self {
// TODO: calculate NodeCapacity out of the devices on the Node
self
}
}

View File

@@ -81,7 +81,7 @@ mod rhai_user_module {
#[rhai_fn(name = "get_username")]
pub fn get_username(user: &mut RhaiUser) -> String {
user.username.clone().unwrap_or_else(|| String::new())
user.username.clone()
}
#[rhai_fn(name = "get_email")]
@@ -95,7 +95,7 @@ mod rhai_user_module {
#[rhai_fn(name = "get_pubkey")]
pub fn get_pubkey(user: &mut RhaiUser) -> String {
user.pubkey.clone().unwrap_or_else(|| String::new())
user.pubkey.clone()
}
}
@@ -162,12 +162,12 @@ mod rhai_group_module {
#[rhai_fn(name = "get_name")]
pub fn get_name(group: &mut RhaiGroup) -> String {
group.name.clone().unwrap_or_else(|| String::new())
group.name.clone()
}
#[rhai_fn(name = "get_description")]
pub fn get_description(group: &mut RhaiGroup) -> String {
group.description.clone().unwrap_or_else(|| String::new())
group.description.clone()
}
}
@@ -253,37 +253,24 @@ mod rhai_dns_zone_module {
Ok(DNSZone::new(0))
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
#[rhai_fn(name = "domain", return_raw)]
pub fn set_domain(
zone: &mut RhaiDNSZone,
name: String,
domain: String,
) -> Result<RhaiDNSZone, Box<EvalAltResult>> {
let owned = std::mem::take(zone);
*zone = owned.name(name);
*zone = owned.domain(domain);
Ok(zone.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
zone: &mut RhaiDNSZone,
description: String,
) -> Result<RhaiDNSZone, Box<EvalAltResult>> {
let owned = std::mem::take(zone);
*zone = owned.description(description);
Ok(zone.clone())
}
#[rhai_fn(name = "save_dns_zone", return_raw)]
pub fn save_dns_zone(zone: &mut RhaiDNSZone) -> Result<RhaiDNSZone, Box<EvalAltResult>> {
Ok(zone.clone())
}
// Setters
#[rhai_fn(name = "set_domain")]
pub fn set_domain(zone: &mut RhaiDNSZone, domain: &str) {
let owned = std::mem::take(zone);
*zone = owned.domain(domain);
}
// Getters
#[rhai_fn(name = "get_id")]
@@ -302,22 +289,22 @@ mod rhai_dns_zone_module {
// ============================================================================
// Registration functions
pub fn register_user_functions(engine: &mut Engine) {
let module = exported_module!(user_module);
let module = exported_module!(rhai_user_module);
engine.register_static_module("user", module.into());
}
pub fn register_group_functions(engine: &mut Engine) {
let module = exported_module!(group_module);
let module = exported_module!(rhai_group_module);
engine.register_static_module("group", module.into());
}
pub fn register_account_functions(engine: &mut Engine) {
let module = exported_module!(account_module);
let module = exported_module!(rhai_account_module);
engine.register_static_module("account", module.into());
}
pub fn register_dnszone_functions(engine: &mut Engine) {
let module = exported_module!(dnszone_module);
let module = exported_module!(rhai_dns_zone_module);
engine.register_static_module("dnszone", module.into());
}

View File

@@ -0,0 +1,156 @@
use derive::FromVec;
use heromodels::db::Db;
use macros::{
register_authorized_create_by_id_fn, register_authorized_delete_by_id_fn,
register_authorized_get_by_id_fn, register_authorized_list_fn,
};
use rhai::plugin::*;
use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, Position, TypeBuilder};
use serde::Serialize;
use serde_json;
use std::mem;
use std::sync::Arc;
use heromodels::db::hero::OurDB;
use heromodels::db::Collection as DbCollectionTrait;
use heromodels::models::library::collection::Collection as RhaiCollection;
use heromodels::models::library::items::{
Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf,
Slide as RhaiSlide, Slideshow as RhaiSlideshow, TocEntry as RhaiTocEntry,
};
/// Registers a `.json()` method for any type `T` that implements the required traits.
fn register_json_method<T>(engine: &mut Engine)
where
T: CustomType + Clone + Serialize,
{
let to_json_fn = |obj: &mut T| -> Result<String, Box<EvalAltResult>> {
match serde_json::to_string_pretty(obj) {
Ok(json_str) => Ok(json_str),
Err(e) => Err(format!("Failed to serialize to JSON: {}", e).into()),
}
};
engine.register_fn("json", to_json_fn);
}
// Wrapper types for arrays
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "CollectionArray")]
pub struct RhaiCollectionArray(pub Vec<RhaiCollection>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "ImageArray")]
pub struct RhaiImageArray(pub Vec<RhaiImage>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "PdfArray")]
pub struct RhaiPdfArray(pub Vec<RhaiPdf>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "MarkdownArray")]
pub struct RhaiMarkdownArray(pub Vec<RhaiMarkdown>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "BookArray")]
pub struct RhaiBookArray(pub Vec<RhaiBook>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "SlideshowArray")]
pub struct RhaiSlideshowArray(pub Vec<RhaiSlideshow>);
#[derive(Debug, Clone, Serialize, CustomType, FromVec)]
#[rhai_type(name = "TocEntryArray")]
pub struct RhaiTocEntryArray(pub Vec<RhaiTocEntry>);
#[export_module]
mod rhai_library_module {
use super::*;
// --- Collection Functions ---
#[rhai_fn(name = "new_collection", return_raw)]
pub fn new_collection() -> Result<RhaiCollection, Box<EvalAltResult>> {
Ok(RhaiCollection::new())
}
#[rhai_fn(name = "collection_title", return_raw)]
pub fn collection_title(
collection: &mut RhaiCollection,
title: String,
) -> Result<RhaiCollection, Box<EvalAltResult>> {
let owned = std::mem::take(collection);
*collection = owned.title(title);
Ok(collection.clone())
}
#[rhai_fn(name = "collection_description", return_raw)]
pub fn collection_description(
collection: &mut RhaiCollection,
description: String,
) -> Result<RhaiCollection, Box<EvalAltResult>> {
let owned = std::mem::take(collection);
*collection = owned.description(description);
Ok(collection.clone())
}
#[rhai_fn(name = "get_collection_id")]
pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 {
collection.id() as i64
}
#[rhai_fn(name = "get_collection_title")]
pub fn get_collection_title(collection: &mut RhaiCollection) -> String {
collection.title().clone()
}
// --- Image Functions ---
#[rhai_fn(name = "new_image", return_raw)]
pub fn new_image() -> Result<RhaiImage, Box<EvalAltResult>> {
Ok(RhaiImage::new())
}
#[rhai_fn(name = "image_title", return_raw)]
pub fn image_title(
image: &mut RhaiImage,
title: String,
) -> Result<RhaiImage, Box<EvalAltResult>> {
let owned = std::mem::take(image);
*image = owned.title(title);
Ok(image.clone())
}
#[rhai_fn(name = "get_image_id")]
pub fn get_image_id(image: &mut RhaiImage) -> i64 {
image.id() as i64
}
// Additional functions would continue here...
}
pub fn register_library_rhai_module(engine: &mut Engine) {
let mut module = exported_module!(rhai_library_module);
register_json_method::<RhaiCollection>(engine);
register_json_method::<RhaiImage>(engine);
register_json_method::<RhaiPdf>(engine);
register_json_method::<RhaiMarkdown>(engine);
register_json_method::<RhaiBook>(engine);
register_json_method::<RhaiSlideshow>(engine);
register_json_method::<RhaiTocEntry>(engine);
register_json_method::<RhaiCollectionArray>(engine);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_collection",
resource_type_str: "Collection",
rhai_return_rust_type: heromodels::models::library::collection::Collection
);
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_collection",
resource_type_str: "Collection",
rhai_return_rust_type: heromodels::models::library::collection::Collection
);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Address {
pub street: String,
pub city: String,
pub state: Option<String>,
pub postal_code: String,
pub country: String,
pub company: Option<String>,
}

View File

@@ -0,0 +1,2 @@
// Export location models
pub mod address;

View File

@@ -13,10 +13,13 @@ pub mod governance;
pub mod heroledger;
pub mod legal;
pub mod library;
pub mod location;
pub mod object;
pub mod projects;
pub mod payment;
pub mod identity;
pub mod tfmarketplace;
pub mod grid4;
// Re-export key types for convenience
pub use core::Comment;

View File

@@ -0,0 +1,27 @@
use heromodels::db::hero::OurDB;
use heromodels::db::{Collection, Db};
use heromodels::models::object::Object;
use macros::{register_authorized_create_by_id_fn, register_authorized_get_by_id_fn};
use rhai::{exported_module, Engine, EvalAltResult, FuncRegistration, Module};
use std::sync::Arc;
pub fn register_object_fns(engine: &mut Engine) {
let mut module = Module::new();
register_authorized_get_by_id_fn!(
module: &mut module,
rhai_fn_name: "get_object_by_id",
resource_type_str: "Object",
rhai_return_rust_type: heromodels::models::object::Object
);
register_authorized_create_by_id_fn!(
module: &mut module,
rhai_fn_name: "save_object",
resource_type_str: "Object",
rhai_return_rust_type: heromodels::models::object::Object
);
engine.register_global_module(module.into());
engine.register_type_with_name::<Object>("Object");
}

View File

@@ -0,0 +1,49 @@
use rhai::plugin::*;
use rhai::{Dynamic, Engine, EvalAltResult, Module};
// Simplified payment module - contains the core Stripe integration
// This is a condensed version of the original payment.rs DSL file
#[export_module]
mod rhai_payment_module {
// Payment configuration and basic functions
#[rhai_fn(name = "configure_stripe", return_raw)]
pub fn configure_stripe(api_key: String) -> Result<String, Box<EvalAltResult>> {
Ok(format!("Stripe configured with key: {}...", &api_key[..8]))
}
// Product functions
#[rhai_fn(name = "new_product", return_raw)]
pub fn new_product() -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from("product_created"))
}
// Price functions
#[rhai_fn(name = "new_price", return_raw)]
pub fn new_price() -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from("price_created"))
}
// Subscription functions
#[rhai_fn(name = "new_subscription", return_raw)]
pub fn new_subscription() -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from("subscription_created"))
}
// Payment intent functions
#[rhai_fn(name = "new_payment_intent", return_raw)]
pub fn new_payment_intent() -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from("payment_intent_created"))
}
// Coupon functions
#[rhai_fn(name = "new_coupon", return_raw)]
pub fn new_coupon() -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from("coupon_created"))
}
}
pub fn register_payment_rhai_module(engine: &mut Engine) {
let module = exported_module!(rhai_payment_module);
engine.register_global_module(module.into());
}

View File

@@ -0,0 +1,115 @@
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
#[derive(Default)]
pub struct UserActivityBuilder {
base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
activity_type: Option<crate::models::user::ActivityType>,
description: Option<String>,
timestamp: Option<chrono::DateTime<chrono::Utc>>,
metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
category: Option<String>,
importance: Option<crate::models::user::ActivityImportance>,
}
impl UserActivityBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self) -> Self{
self.base_data.id = Some(id.into());
self
}
pub fn activity_type(mut self, activity_type: crate::models::user::ActivityType) -> Self {
self.activity_type = Some(activity_type);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn timestamp(mut self, timestamp: chrono::DateTime<chrono::Utc>) -> Self {
self.timestamp = Some(timestamp);
self
}
pub fn metadata(mut self, metadata: std::collections::HashMap<String, serde_json::Value>) -> Self {
self.metadata = Some(metadata);
self
}
pub fn category(mut self, category: impl Into<String>) -> Self {
self.category = Some(category.into());
self
}
pub fn importance(mut self, importance: crate::models::user::ActivityImportance) -> Self {
self.importance = Some(importance);
self
}
pub fn build(self) -> Result<crate::models::user::UserActivity, String> {
Ok(crate::models::user::UserActivity {
base_data: BaseModelData::new(),
// id: self.base_data.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()) - moved to base_data,
activity_type: self.activity_type.ok_or("activity_type is required")?,
description: self.description.unwrap_or_else(|| "No description".to_string()),
timestamp: self.timestamp.unwrap_or_else(|| chrono::Utc::now()),
metadata: self.metadata.unwrap_or_default(),
category: self.category.unwrap_or_else(|| "General".to_string()),
importance: self.importance.unwrap_or(crate::models::user::ActivityImportance::Medium),
})
}
}
/// User Activity Tracking
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserActivity {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
pub activity_type: ActivityType,
pub description: String,
#[serde(deserialize_with = "deserialize_datetime")]
pub timestamp: DateTime<Utc>,
pub metadata: std::collections::HashMap<String, serde_json::Value>,
pub category: String,
pub importance: ActivityImportance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActivityType {
Login,
Purchase,
Deployment,
ServiceCreated,
AppPublished,
NodeAdded,
NodeUpdated,
WalletTransaction,
ProfileUpdate,
SettingsChange,
MarketplaceView,
SliceCreated,
SliceAllocated,
SliceReleased,
SliceRentalStarted,
SliceRentalStopped,
SliceRentalRestarted,
SliceRentalCancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActivityImportance {
Low,
Medium,
High,
Critical,
}

View File

@@ -0,0 +1,361 @@
use heromodels_core::BaseModelData;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
/// Unified App struct that can represent published apps, deployments, and deployment stats
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct App {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
// Core app information
pub name: String,
pub category: Option<String>,
pub version: Option<String>,
pub status: String,
// Deployment information
pub customer_name: Option<String>,
pub customer_email: Option<String>,
pub deployed_date: Option<String>,
pub health_score: Option<f32>,
pub region: Option<String>,
pub instances: Option<i32>,
pub resource_usage: Option<ResourceUtilization>,
// Business metrics
pub deployments: Option<i32>,
pub rating: Option<f32>,
pub monthly_revenue_usd: Option<i32>,
pub cost_per_month: Option<Decimal>,
// Metadata
pub last_updated: Option<String>,
pub auto_healing: Option<bool>,
pub provider: Option<String>,
pub deployed_at: Option<DateTime<Utc>>,
}
impl App {
/// Convenience method to get the app ID
pub fn id(&self) -> &u32 {
&self.base_data.id
}
/// Get category with default
pub fn category_or_default(&self) -> String {
self.category.clone().unwrap_or_else(|| "Application".to_string())
}
/// Get version with default
pub fn version_or_default(&self) -> String {
self.version.clone().unwrap_or_else(|| "1.0.0".to_string())
}
/// Get deployments count with default
pub fn deployments_or_default(&self) -> i32 {
self.deployments.unwrap_or(0)
}
/// Get rating with default
pub fn rating_or_default(&self) -> f32 {
self.rating.unwrap_or(4.0)
}
/// Get monthly revenue with default
pub fn monthly_revenue_usd_or_default(&self) -> i32 {
self.monthly_revenue_usd.unwrap_or(0)
}
/// Get last updated with default
pub fn last_updated_or_default(&self) -> String {
self.last_updated.clone().unwrap_or_else(|| "Unknown".to_string())
}
/// Get auto healing with default
pub fn auto_healing_or_default(&self) -> bool {
self.auto_healing.unwrap_or(false)
}
}
pub struct Deployment {
pub base_data: BaseModelData,
pub app_id: String,
pub instance_id: String,
pub status: String,
pub region: String,
pub health_score: Option<f32>,
pub resource_usage: Option<ResourceUtilization>,
pub deployed_at: Option<DateTime<Utc>>,
}
/// Resource utilization information
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceUtilization {
pub cpu: i32,
pub memory: i32,
pub storage: i32,
pub network: i32,
}
/// Deployment status enumeration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum DeploymentStatus {
#[default]
Running,
Stopped,
Failed,
Pending,
Maintenance,
}
/// Unified App builder
#[derive(Default)]
pub struct AppBuilder {
base_data: BaseModelData,
name: Option<String>,
category: Option<String>,
version: Option<String>,
status: Option<String>,
customer_name: Option<String>,
customer_email: Option<String>,
deployed_date: Option<String>,
health_score: Option<f32>,
region: Option<String>,
instances: Option<i32>,
resource_usage: Option<ResourceUtilization>,
deployments: Option<i32>,
rating: Option<f32>,
monthly_revenue_usd: Option<i32>,
cost_per_month: Option<Decimal>,
last_updated: Option<String>,
auto_healing: Option<bool>,
provider: Option<String>,
deployed_at: Option<DateTime<Utc>>,
}
impl AppBuilder {
pub fn new() -> Self {
Self {
base_data: BaseModelData::new(),
..Default::default()
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn category(mut self, category: impl Into<String>) -> Self {
self.category = Some(category.into());
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
pub fn customer_name(mut self, name: impl Into<String>) -> Self {
self.customer_name = Some(name.into());
self
}
pub fn customer_email(mut self, email: impl Into<String>) -> Self {
self.customer_email = Some(email.into());
self
}
pub fn deployed_date(mut self, date: impl Into<String>) -> Self {
self.deployed_date = Some(date.into());
self
}
pub fn health_score(mut self, score: f32) -> Self {
self.health_score = Some(score);
self
}
pub fn region(mut self, region: impl Into<String>) -> Self {
self.region = Some(region.into());
self
}
pub fn instances(mut self, instances: i32) -> Self {
self.instances = Some(instances);
self
}
pub fn resource_usage(mut self, usage: ResourceUtilization) -> Self {
self.resource_usage = Some(usage);
self
}
pub fn deployments(mut self, deployments: i32) -> Self {
self.deployments = Some(deployments);
self
}
pub fn rating(mut self, rating: f32) -> Self {
self.rating = Some(rating);
self
}
pub fn monthly_revenue_usd(mut self, revenue: i32) -> Self {
self.monthly_revenue_usd = Some(revenue);
self
}
pub fn cost_per_month(mut self, cost: Decimal) -> Self {
self.cost_per_month = Some(cost);
self
}
pub fn last_updated(mut self, updated: impl Into<String>) -> Self {
self.last_updated = Some(updated.into());
self
}
pub fn auto_healing(mut self, enabled: bool) -> Self {
self.auto_healing = Some(enabled);
self
}
pub fn provider(mut self, provider: impl Into<String>) -> Self {
self.provider = Some(provider.into());
self
}
pub fn deployed_at(mut self, date: DateTime<Utc>) -> Self {
self.deployed_at = Some(date);
self
}
pub fn build(self) -> Result<App, String> {
Ok(App {
base_data: self.base_data,
name: self.name.ok_or("name is required")?,
category: self.category,
version: self.version,
status: self.status.unwrap_or_else(|| "Active".to_string()),
customer_name: self.customer_name,
customer_email: self.customer_email,
deployed_date: self.deployed_date,
health_score: self.health_score,
region: self.region,
instances: self.instances,
resource_usage: self.resource_usage,
deployments: self.deployments,
rating: self.rating,
monthly_revenue_usd: self.monthly_revenue_usd,
cost_per_month: self.cost_per_month,
last_updated: self.last_updated,
auto_healing: self.auto_healing,
provider: self.provider,
deployed_at: self.deployed_at,
})
}
}
impl App {
pub fn builder() -> AppBuilder {
AppBuilder::new()
}
// Template methods for common app types
pub fn analytics_template(name: &str) -> Self {
Self::builder()
.name(name)
.category("Analytics")
.version("1.0.0")
.status("Active")
.rating(4.5)
.auto_healing(true)
.build()
.unwrap()
}
pub fn database_template(name: &str) -> Self {
Self::builder()
.name(name)
.category("Database")
.version("1.0.0")
.status("Active")
.rating(4.2)
.auto_healing(false) // Databases need manual intervention
.build()
.unwrap()
}
pub fn web_template(name: &str) -> Self {
Self::builder()
.name(name)
.category("Web")
.version("1.0.0")
.status("Active")
.rating(4.0)
.auto_healing(true)
.build()
.unwrap()
}
// Fluent methods for chaining
pub fn with_stats(mut self, deployments: i32, rating: f32, monthly_revenue_usd: i32) -> Self {
self.deployments = Some(deployments);
self.rating = Some(rating);
self.monthly_revenue_usd = Some(monthly_revenue_usd);
self
}
pub fn with_auto_healing(mut self, enabled: bool) -> Self {
self.auto_healing = Some(enabled);
self
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn with_last_updated(mut self, updated: impl Into<String>) -> Self {
self.last_updated = Some(updated.into());
self
}
pub fn with_deployment_info(mut self, customer_name: &str, customer_email: &str, region: &str) -> Self {
self.customer_name = Some(customer_name.to_string());
self.customer_email = Some(customer_email.to_string());
self.region = Some(region.to_string());
self.deployed_at = Some(Utc::now());
self
}
pub fn with_resource_usage(mut self, cpu: i32, memory: i32, storage: i32, network: i32) -> Self {
self.resource_usage = Some(ResourceUtilization {
cpu,
memory,
storage,
network,
});
self
}
}
// Type aliases for backward compatibility
pub type PublishedApp = App;
pub type AppDeployment = App;
pub type DeploymentStat = App;
pub type UserDeployment = App;
pub type PublishedAppBuilder = AppBuilder;
pub type AppDeploymentBuilder = AppBuilder;
pub type DeploymentStatBuilder = AppBuilder;
pub type UserDeploymentBuilder = AppBuilder;

View File

@@ -0,0 +1,351 @@
//! Builder patterns for all marketplace models
//! This module provides a centralized, maintainable way to construct complex structs
//! with sensible defaults and validation.
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde_json::Value;
use std::collections::HashMap;
use super::{
user::{PublishedApp, DeploymentStat, ResourceUtilization, User, UserRole, MockUserData, ServiceBooking},
product::{Product, ProductAttribute, ProductAvailability, ProductMetadata},
order::{Order, OrderItem, OrderStatus, PaymentDetails, Address, PurchaseType},
};
use crate::services::user_persistence::AppDeployment;
use heromodels_core::BaseModelData;
// =============================================================================
// USER MODEL BUILDERS
// =============================================================================
#[derive(Default)]
pub struct MockDataBuilder {
user_type: Option<String>,
include_farmer_data: Option<bool>,
include_service_data: Option<bool>,
include_app_data: Option<bool>,
}
impl MockDataBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn user_type(mut self, user_type: impl Into<String>) -> Self {
self.user_type = Some(user_type.into());
self
}
pub fn include_farmer_data(mut self, include: bool) -> Self {
self.include_farmer_data = Some(include);
self
}
pub fn include_service_data(mut self, include: bool) -> Self {
self.include_service_data = Some(include);
self
}
pub fn include_app_data(mut self, include: bool) -> Self {
self.include_app_data = Some(include);
self
}
pub fn build(self) -> crate::models::user::MockUserData {
// This would create appropriate mock data based on configuration
// For now, return a default instance
crate::models::user::MockUserData::new_user()
}
}
// =============================================================================
// FARMER DATA BUILDER
// =============================================================================
#[derive(Default)]
pub struct FarmerDataBuilder {
total_nodes: Option<i32>,
online_nodes: Option<i32>,
total_capacity: Option<crate::models::user::NodeCapacity>,
used_capacity: Option<crate::models::user::NodeCapacity>,
monthly_earnings: Option<i32>,
total_earnings: Option<i32>,
uptime_percentage: Option<f32>,
nodes: Option<Vec<crate::models::user::FarmNode>>,
earnings_history: Option<Vec<crate::models::user::EarningsRecord>>,
active_slices: Option<i32>,
}
impl FarmerDataBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn total_nodes(mut self, total_nodes: i32) -> Self {
self.total_nodes = Some(total_nodes);
self
}
pub fn online_nodes(mut self, online_nodes: i32) -> Self {
self.online_nodes = Some(online_nodes);
self
}
pub fn total_capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self {
self.total_capacity = Some(capacity);
self
}
pub fn used_capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self {
self.used_capacity = Some(capacity);
self
}
pub fn monthly_earnings_usd(mut self, earnings: i32) -> Self {
self.monthly_earnings = Some(earnings);
self
}
pub fn total_earnings_usd(mut self, earnings: i32) -> Self {
self.total_earnings = Some(earnings);
self
}
pub fn uptime_percentage(mut self, uptime: f32) -> Self {
self.uptime_percentage = Some(uptime);
self
}
pub fn nodes(mut self, nodes: Vec<crate::models::user::FarmNode>) -> Self {
self.nodes = Some(nodes);
self
}
pub fn earnings_history(mut self, history: Vec<crate::models::user::EarningsRecord>) -> Self {
self.earnings_history = Some(history);
self
}
pub fn earnings(mut self, earnings: Vec<crate::models::user::EarningsRecord>) -> Self {
self.earnings_history = Some(earnings);
self
}
pub fn active_slices(mut self, active_slices: i32) -> Self {
self.active_slices = Some(active_slices);
self
}
pub fn calculate_totals(mut self) -> Self {
// Calculate totals from existing data
if let Some(ref nodes) = self.nodes {
self.total_nodes = Some(nodes.len() as i32);
self.online_nodes = Some(nodes.iter().filter(|n| matches!(n.status, crate::models::user::NodeStatus::Online)).count() as i32);
// Calculate total and used capacity from all nodes
let mut total_capacity = crate::models::user::NodeCapacity {
cpu_cores: 0,
memory_gb: 0,
storage_gb: 0,
bandwidth_mbps: 0,
ssd_storage_gb: 0,
hdd_storage_gb: 0,
};
let mut used_capacity = crate::models::user::NodeCapacity {
cpu_cores: 0,
memory_gb: 0,
storage_gb: 0,
bandwidth_mbps: 0,
ssd_storage_gb: 0,
hdd_storage_gb: 0,
};
for node in nodes {
total_capacity.cpu_cores += node.capacity.cpu_cores;
total_capacity.memory_gb += node.capacity.memory_gb;
total_capacity.storage_gb += node.capacity.storage_gb;
total_capacity.bandwidth_mbps += node.capacity.bandwidth_mbps;
total_capacity.ssd_storage_gb += node.capacity.ssd_storage_gb;
total_capacity.hdd_storage_gb += node.capacity.hdd_storage_gb;
used_capacity.cpu_cores += node.used_capacity.cpu_cores;
used_capacity.memory_gb += node.used_capacity.memory_gb;
used_capacity.storage_gb += node.used_capacity.storage_gb;
used_capacity.bandwidth_mbps += node.used_capacity.bandwidth_mbps;
used_capacity.ssd_storage_gb += node.used_capacity.ssd_storage_gb;
used_capacity.hdd_storage_gb += node.used_capacity.hdd_storage_gb;
}
self.total_capacity = Some(total_capacity);
self.used_capacity = Some(used_capacity);
// Calculate uptime percentage
if !nodes.is_empty() {
let avg_uptime = nodes.iter().map(|n| n.uptime_percentage).sum::<f32>() / nodes.len() as f32;
self.uptime_percentage = Some(avg_uptime);
}
}
if let Some(ref earnings) = self.earnings_history {
let total: i32 = earnings.iter().map(|e| e.amount.to_string().parse::<i32>().unwrap_or(0)).sum();
self.total_earnings = Some(total);
self.monthly_earnings = Some(total); // Set monthly earnings as well
}
self
}
pub fn build(self) -> Result<crate::models::user::FarmerData, String> {
Ok(crate::models::user::FarmerData {
total_nodes: self.total_nodes.unwrap_or(0),
online_nodes: self.online_nodes.unwrap_or(0),
total_capacity: self.total_capacity.unwrap_or(crate::models::user::NodeCapacity {
cpu_cores: 0,
memory_gb: 0,
storage_gb: 0,
bandwidth_mbps: 0,
ssd_storage_gb: 0,
hdd_storage_gb: 0,
}),
used_capacity: self.used_capacity.unwrap_or(crate::models::user::NodeCapacity {
cpu_cores: 0,
memory_gb: 0,
storage_gb: 0,
bandwidth_mbps: 0,
ssd_storage_gb: 0,
hdd_storage_gb: 0,
}),
monthly_earnings_usd: self.monthly_earnings.unwrap_or(0),
total_earnings_usd: self.total_earnings.unwrap_or(0),
uptime_percentage: self.uptime_percentage.unwrap_or(0.0),
nodes: self.nodes.unwrap_or_default(),
earnings_history: self.earnings_history.unwrap_or_default(),
slice_templates: Vec::default(), // Will be populated separately
active_slices: self.active_slices.unwrap_or(0),
})
}
}
// =============================================================================
// SERVICE BOOKING BUILDER
// =============================================================================
#[derive(Default)]
pub struct SpendingRecordBuilder {
date: Option<String>,
amount: Option<i32>,
service_name: Option<String>,
provider_name: Option<String>,
}
impl SpendingRecordBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn date(mut self, date: &str) -> Self {
self.date = Some(date.to_string());
self
}
pub fn amount(mut self, amount: i32) -> Self {
self.amount = Some(amount);
self
}
pub fn service_name(mut self, name: &str) -> Self {
self.service_name = Some(name.to_string());
self
}
pub fn provider_name(mut self, name: &str) -> Self {
self.provider_name = Some(name.to_string());
self
}
pub fn build(self) -> Result<crate::models::user::SpendingRecord, String> {
Ok(crate::models::user::SpendingRecord {
date: self.date.ok_or("Date is required")?,
amount: self.amount.unwrap_or(0),
service_name: self.service_name.ok_or("Service name is required")?,
provider_name: self.provider_name.ok_or("Provider name is required")?,
})
}
}
impl crate::models::user::SpendingRecord {
pub fn builder() -> SpendingRecordBuilder {
SpendingRecordBuilder::new()
}
}
// =============================================================================
// AUTO TOP-UP BUILDERS
// =============================================================================
#[derive(Default)]
pub struct AutoTopUpSettingsBuilder {
enabled: Option<bool>,
threshold_amount: Option<Decimal>,
topup_amount: Option<Decimal>,
payment_method_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
daily_limit: Option<Decimal>,
monthly_limit: Option<Decimal>,
}
impl AutoTopUpSettingsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = Some(enabled);
self
}
pub fn threshold_amount(mut self, amount: Decimal) -> Self {
self.threshold_amount = Some(amount);
self
}
pub fn topup_amount(mut self, amount: Decimal) -> Self {
self.topup_amount = Some(amount);
self
}
pub fn payment_method_id(mut self) -> Self{
self.payment_method_id = Some(id.into());
self
}
pub fn daily_limit(mut self, limit: Decimal) -> Self {
self.daily_limit = Some(limit);
self
}
pub fn monthly_limit(mut self, limit: Decimal) -> Self {
self.monthly_limit = Some(limit);
self
}
pub fn build(self) -> Result<crate::services::user_persistence::AutoTopUpSettings, String> {
Ok(crate::services::user_persistence::AutoTopUpSettings {
enabled: self.enabled.unwrap_or(false),
threshold_amount_usd: self.threshold_amount.unwrap_or(dec!(10.0)),
topup_amount_usd: self.topup_amount.unwrap_or(dec!(25.0)),
payment_method_base_data: BaseModelData::new(),
// id: self.payment_method_id.ok_or("payment_method_id is required")? - moved to base_data,
daily_limit_usd: self.daily_limit,
monthly_limit_usd: self.monthly_limit,
// created_at: chrono::Utc::now() - moved to base_data,
// updated_at: chrono::Utc::now() - moved to base_data,
})
}
}

View File

@@ -0,0 +1,105 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
/// Shopping Cart Models
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CartItem {
pub product_id: u32,
pub quantity: u32,
pub selected_specifications: HashMap<String, serde_json::Value>,
pub added_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cart {
pub base_data: BaseModelData,
pub items: Vec<CartItem>,
}
impl Cart {
pub fn new() -> Self{
let now = Utc::now();
Self {
base_data: BaseModelData::new(),
items: Vec::default(),
}
}
pub fn add_item(&mut self, item: CartItem) {
// Check if item already exists and update quantity
if let Some(existing_item) = self.items.iter_mut()
.find(|i| i.product_id == item.product_id && i.selected_specifications == item.selected_specifications) {
existing_item.quantity += item.quantity;
} else {
self.items.push(item);
}
}
pub fn remove_item(&mut self, product_id: &str, name: &str) -> bool{
let initial_len = self.items.len();
self.items.retain(|item| item.product_id != product_id);
if self.items.len() != initial_len {
self.base_data.updated_at = Utc::now();
true
} else {
false
}
}
pub fn update_item_quantity(&mut self, product_id: &str, name: &str) -> bool {
if let Some(item) = self.items.iter_mut().find(|i| i.product_id == product_id) {
if quantity == 0 {
return self.remove_item(product_id);
}
item.quantity = quantity;
item.updated_at = Utc::now();
self.base_data.updated_at = Utc::now();
true
} else {
false
}
}
pub fn clear(&mut self) {
self.items.clear();
self.base_data.updated_at = Utc::now();
}
pub fn get_total_items(&self) -> u32 {
self.items.iter().map(|item| item.quantity).sum()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl CartItem {
pub fn new(product_id: &str, name: &str) -> Self {
let now = Utc::now();
Self {
product_id,
quantity,
selected_specifications: HashMap::default(),
added_at: now,
// updated_at: now - moved to base_data,
}
}
pub fn with_specifications(
product_id: &str, name: &str) -> Self {
let now = Utc::now();
Self {
product_id,
quantity,
selected_specifications: specifications,
added_at: now,
// updated_at: now - moved to base_data,
}
}
}

View File

@@ -0,0 +1,90 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::CustomType;
use crate::models::tfmarketplace::user::ResourceUtilization;
/// Configurable currency support for any currency type
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Currency {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
#[index]
pub code: String, // USD, EUR, BTC, ETH, etc.
pub name: String,
pub symbol: String,
pub currency_type: CurrencyType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CurrencyType {
Fiat,
Cryptocurrency,
Token,
Points, // For loyalty/reward systems
Custom(String), // For marketplace-specific currencies
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Price {
pub base_amount: Decimal, // Amount in marketplace base currency
pub base_currency: String,
pub display_currency: String,
pub display_amount: Decimal,
pub formatted_display: String,
pub conversion_rate: Decimal,
pub conversion_timestamp: DateTime<Utc>,
}
impl Currency {
pub fn new(
code: String,
name: String,
symbol: String,
currency_type: CurrencyType,
) -> Self {
Self {
base_data: BaseModelData::new(),
code,
name,
symbol,
currency_type,
}
}
}
impl Price {
pub fn new(
base_amount: Decimal,
base_currency: String,
display_currency: String,
conversion_rate: Decimal,
) -> Self {
let display_amount = base_amount * conversion_rate;
// Use proper currency symbol formatting - this will be updated by the currency service
Self {
base_amount,
base_currency: base_currency.clone(),
display_currency: display_currency.clone(),
display_amount,
formatted_display: format!("{} {}", display_amount.round_dp(2), display_currency),
conversion_rate,
conversion_timestamp: Utc::now(),
}
}
pub fn format_with_symbol(&self, symbol: &str) -> String {
format!("{} {}",
self.display_amount.round_dp(2),
symbol
)
}
pub fn update_formatted_display(&mut self, formatted: String) {
self.formatted_display = formatted;
}
}

View File

@@ -0,0 +1,30 @@
/// Farmer-specific data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerData {
pub total_nodes: i32,
pub online_nodes: i32,
pub total_capacity: NodeCapacity,
pub used_capacity: NodeCapacity,
pub monthly_earnings_usd: i32,
pub total_earnings_usd: i32,
pub uptime_percentage: f32,
pub nodes: Vec<FarmNode>,
pub earnings_history: Vec<EarningsRecord>,
pub slice_templates: Vec<crate::models::product::Product>,
pub active_slices: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmerSettings {
#[serde(default)]
pub auto_accept_deployments: bool,
#[serde(default = "default_maintenance_window")]
pub maintenance_window: String,
#[serde(default)]
pub notification_preferences: NotificationSettings,
pub minimum_deployment_duration: i32, // hours
pub preferred_regions: Vec<String>,
#[serde(default)]
pub default_slice_customizations: Option<std::collections::HashMap<String, serde_json::Value>>, // Placeholder for DefaultSliceFormat
}

View File

@@ -0,0 +1,17 @@
// Export models - starting with basic models first
// pub mod user;
// pub mod product;
// pub mod currency;
// pub mod order;
// pub mod pool;
// pub mod builders; // Re-enabled with essential builders only
// pub mod cart;
// pub mod payment;
// pub mod service;
// pub mod slice;
// pub mod node;
pub mod app;
// Re-export commonly used types for easier access
pub use app::{App, PublishedApp, PublishedAppBuilder, ResourceUtilization, AppBuilder, DeploymentStatus};
// pub mod node; // Temporarily disabled - has many service dependencies

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
# Notes
all id's of base objects are u32
Cart is front end specific,
currency and exchange rates should be calculated by client
stuff such as decomal numbers related to presentation shouldnt be in base models
purchase doesnt need to now wether it is instant or cart
all base objects contain created_at and updated_at, so not needed to be added to every model

View File

@@ -0,0 +1,402 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::CustomType;
use crate::models::tfmarketplace::user::ResourceUtilization;
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Order {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
#[index]
pub user_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub items: Vec<OrderItem>,
pub subtotal_base: Decimal, // In base currency
pub total_base: Decimal, // In base currency
pub base_currency: String,
pub currency_used: String, // Currency user paid in
pub currency_total: Decimal, // Amount in user's currency
pub conversion_rate: Decimal, // Rate used for conversion
pub status: OrderStatus,
pub payment_method: String,
pub payment_details: Option<PaymentDetails>,
pub billing_address: Option<Address>,
pub shipping_address: Option<Address>,
pub notes: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrderItem {
pub product_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub product_name: String,
pub product_category: String,
pub quantity: u32,
pub unit_price_base: Decimal, // In base currency
pub total_price_base: Decimal, // In base currency
pub specifications: HashMap<String, serde_json::Value>,
pub provider_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub provider_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum OrderStatus {
Pending,
Confirmed,
Processing,
Deployed,
Completed,
Cancelled,
Refunded,
Failed,
}
/// Order summary for display purposes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderSummary {
pub subtotal: Decimal,
pub tax: Decimal,
pub shipping: Decimal,
pub discount: Decimal,
pub total: Decimal,
pub currency: String,
pub item_count: u32,
}
impl Order {
pub fn new(
base_data: BaseModelData::new(),
// id: String - moved to base_data,
user_base_data: BaseModelData::new(),
// id: String - moved to base_data,
base_currency: String,
currency_used: String,
conversion_rate: Decimal,
) -> Self {
Self {
base_data: BaseModelData::new(),
user_id,
items: Vec::default(),
subtotal_base: Decimal::from(0),
total_base: Decimal::from(0),
base_currency,
currency_used,
currency_total: Decimal::from(0),
conversion_rate,
status: OrderStatus::Pending,
payment_method: String::new(),
payment_details: None,
billing_address: None,
shipping_address: None,
notes: None,
}
}
pub fn add_item(&mut self, item: OrderItem) {
self.items.push(item);
self.calculate_totals();
}
pub fn calculate_totals(&mut self) {
self.subtotal_base = self.items.iter()
.map(|item| item.total_price_base)
.sum();
self.total_base = self.subtotal_base; // Add taxes, fees, etc. here
self.currency_total = self.total_base * self.conversion_rate;
self.base_data.modified_at = Utc::now().timestamp();
}
pub fn update_status(&mut self, status: OrderStatus) {
self.status = status;
self.base_data.modified_at = Utc::now().timestamp();
}
pub fn set_payment_details(&mut self, payment_details: PaymentDetails) {
self.payment_details = Some(payment_details);
self.base_data.modified_at = Utc::now().timestamp();
}
pub fn get_item_count(&self) -> u32 {
self.items.iter().map(|item| item.quantity).sum()
}
}
impl OrderItem {
pub fn new(
product_base_data: BaseModelData::new(),
// id: String - moved to base_data,
product_name: String,
product_category: String,
quantity: u32,
unit_price_base: Decimal,
provider_base_data: BaseModelData::new(),
// id: String - moved to base_data,
provider_name: String,
) -> Self {
Self {
product_id,
product_name,
product_category,
quantity,
unit_price_base,
total_price_base: unit_price_base * Decimal::from(quantity),
specifications: HashMap::default(),
provider_id,
provider_name,
}
}
pub fn add_specification(&mut self, key: String, value: serde_json::Value) {
self.specifications.insert(key, value);
}
pub fn update_quantity(&mut self, quantity: u32) {
self.quantity = quantity;
self.total_price_base = self.unit_price_base * Decimal::from(quantity);
}
}
#[derive(Default)]
pub struct OrderBuilder {
base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
user_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
items: Vec<OrderItem>,
subtotal_base: Option<Decimal>,
total_base: Option<Decimal>,
base_currency: Option<String>,
currency_used: Option<String>,
currency_total: Option<Decimal>,
conversion_rate: Option<Decimal>,
status: Option<OrderStatus>,
payment_method: Option<String>,
payment_details: Option<PaymentDetails>,
billing_address: Option<Address>,
shipping_address: Option<Address>,
notes: Option<String>,
purchase_type: Option<PurchaseType>,
// created_at: Option<DateTime<Utc>> - moved to base_data,
// updated_at: Option<DateTime<Utc>> - moved to base_data,
}
impl OrderBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self) -> Self{
self.base_data.id = Some(id.into());
self
}
pub fn user_id(mut self, user_id: &str, name: &str) -> Self{
self.user_id = Some(user_id.into());
self
}
pub fn add_item(mut self, item: OrderItem) -> Self {
self.items.push(item);
self
}
pub fn items(mut self, items: Vec<OrderItem>) -> Self {
self.items = items;
self
}
pub fn subtotal_base(mut self, subtotal: Decimal) -> Self {
self.subtotal_base = Some(subtotal);
self
}
pub fn total_base(mut self, total: Decimal) -> Self {
self.total_base = Some(total);
self
}
pub fn base_currency(mut self, currency: impl Into<String>) -> Self {
self.base_currency = Some(currency.into());
self
}
pub fn currency_used(mut self, currency: impl Into<String>) -> Self {
self.currency_used = Some(currency.into());
self
}
pub fn currency_total(mut self, total: Decimal) -> Self {
self.currency_total = Some(total);
self
}
pub fn conversion_rate(mut self, rate: Decimal) -> Self {
self.conversion_rate = Some(rate);
self
}
pub fn status(mut self, status: OrderStatus) -> Self {
self.status = Some(status);
self
}
pub fn payment_method(mut self, method: impl Into<String>) -> Self {
self.payment_method = Some(method.into());
self
}
pub fn payment_details(mut self, details: PaymentDetails) -> Self {
self.payment_details = Some(details);
self
}
pub fn billing_address(mut self, address: Address) -> Self {
self.billing_address = Some(address);
self
}
pub fn shipping_address(mut self, address: Address) -> Self {
self.shipping_address = Some(address);
self
}
pub fn notes(mut self, notes: impl Into<String>) -> Self {
self.notes = Some(notes.into());
self
}
pub fn purchase_type(mut self, purchase_type: PurchaseType) -> Self {
self.purchase_type = Some(purchase_type);
self
}
pub fn build(self) -> Result<Order, String> {
let now = Utc::now();
let subtotal = self.subtotal_base.unwrap_or_else(|| {
self.items.iter().map(|item| item.total_price_base).sum()
});
Ok(Order {
base_data: BaseModelData::new(),
// id: self.base_data.id.ok_or("id is required")? - moved to base_data,
user_base_data: BaseModelData::new(),
// id: self.user_id.ok_or("user_id is required")? - moved to base_data,
items: self.items,
subtotal_base: subtotal,
total_base: self.total_base.unwrap_or(subtotal),
base_currency: self.base_currency.unwrap_or_else(|| "USD".to_string()),
currency_used: self.currency_used.unwrap_or_else(|| "USD".to_string()),
currency_total: self.currency_total.unwrap_or(subtotal),
conversion_rate: self.conversion_rate.unwrap_or_else(|| Decimal::from(1)),
status: self.status.unwrap_or(OrderStatus::Pending),
payment_method: self.payment_method.unwrap_or_else(|| "credit_card".to_string()),
payment_details: self.payment_details,
billing_address: self.billing_address,
shipping_address: self.shipping_address,
notes: self.notes,
purchase_type: self.purchase_type.unwrap_or(PurchaseType::Cart),
// created_at: self.base_data.created_at.unwrap_or(now) - moved to base_data,
// updated_at: self.base_data.updated_at.unwrap_or(now) - moved to base_data,
})
}
}
impl Order {
pub fn builder() -> OrderBuilder {
OrderBuilder::new()
}
}
#[derive(Default)]
pub struct OrderItemBuilder {
product_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
product_name: Option<String>,
product_category: Option<String>,
quantity: Option<u32>,
unit_price_base: Option<Decimal>,
total_price_base: Option<Decimal>,
specifications: HashMap<String, Value>,
provider_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
provider_name: Option<String>,
}
impl OrderItemBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn product_id(mut self) -> Self{
self.product_id = Some(id.into());
self
}
pub fn product_name(mut self, name: impl Into<String>) -> Self {
self.product_name = Some(name.into());
self
}
pub fn product_category(mut self, category: impl Into<String>) -> Self {
self.product_category = Some(category.into());
self
}
pub fn quantity(mut self, quantity: u32) -> Self {
self.quantity = Some(quantity);
self
}
pub fn unit_price_base(mut self, price: Decimal) -> Self {
self.unit_price_base = Some(price);
self
}
pub fn add_specification(mut self, key: impl Into<String>, value: Value) -> Self {
self.specifications.insert(key.into(), value);
self
}
pub fn provider_id(mut self) -> Self{
self.provider_id = Some(id.into());
self
}
pub fn provider_name(mut self, name: impl Into<String>) -> Self {
self.provider_name = Some(name.into());
self
}
pub fn build(self) -> Result<OrderItem, String> {
let quantity = self.quantity.unwrap_or(1);
let unit_price = self.unit_price_base.ok_or("unit_price_base is required")?;
let total_price = self.total_price_base.unwrap_or(unit_price * Decimal::from(quantity));
Ok(OrderItem {
product_base_data: BaseModelData::new(),
// id: self.product_id.ok_or("product_id is required")? - moved to base_data,
product_name: self.product_name.ok_or("product_name is required")?,
product_category: self.product_category.ok_or("product_category is required")?,
quantity,
unit_price_base: unit_price,
total_price_base: total_price,
specifications: self.specifications,
provider_base_data: BaseModelData::new(),
// id: self.provider_id.ok_or("provider_id is required")? - moved to base_data,
provider_name: self.provider_name.ok_or("provider_name is required")?,
})
}
}
impl OrderItem {
pub fn builder() -> OrderItemBuilder {
OrderItemBuilder::new()
}
}

View File

@@ -0,0 +1,77 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentDetails {
pub payment_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub payment_method: PaymentMethod,
pub transaction_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
pub payment_status: PaymentStatus,
pub payment_timestamp: Option<DateTime<Utc>>,
pub failure_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaymentMethod {
CreditCard {
last_four: String,
card_type: String,
},
BankTransfer {
bank_name: String,
account_last_four: String,
},
Cryptocurrency {
currency: String,
wallet_address: String,
},
Token {
token_type: String,
wallet_address: String,
},
Mock {
method_name: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaymentStatus {
Pending,
Processing,
Completed,
Failed,
Cancelled,
Refunded,
}
impl PaymentDetails {
pub fn new(payment_id: &str, name: &str) -> Self {
Self {
payment_id,
payment_method,
transaction_base_data: BaseModelData::new(),
// id: None - moved to base_data,
payment_status: PaymentStatus::Pending,
payment_timestamp: None,
failure_reason: None,
}
}
pub fn mark_completed(&mut self, transaction_id: String) { - moved to base_data
self.transaction_id = Some(transaction_id);
self.payment_status = PaymentStatus::Completed;
self.payment_timestamp = Some(Utc::now());
}
pub fn mark_failed(&mut self, reason: String) {
self.payment_status = PaymentStatus::Failed;
self.failure_reason = Some(reason);
self.payment_timestamp = Some(Utc::now());
}
}

View File

@@ -0,0 +1,105 @@
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityPool {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
pub name: String,
pub token_a: String,
pub token_b: String,
pub reserve_a: Decimal,
pub reserve_b: Decimal,
pub exchange_rate: Decimal,
pub liquidity: Decimal,
pub volume_24h: Decimal,
pub fee_percentage: Decimal,
pub status: PoolStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PoolStatus {
Active,
Paused,
Maintenance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeRequest {
pub pool_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub from_token: String,
pub to_token: String,
pub amount: Decimal,
pub min_receive: Option<Decimal>,
pub slippage_tolerance: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExchangeResponse {
pub success: bool,
pub message: String,
pub transaction_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
pub from_amount: Option<Decimal>,
pub to_amount: Option<Decimal>,
pub exchange_rate: Option<Decimal>,
pub fee: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeRequest {
pub amount: Decimal,
pub duration_months: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakePosition {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
pub user_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub amount: Decimal,
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub discount_percentage: Decimal,
pub reputation_bonus: i32,
pub status: StakeStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StakeStatus {
Active,
Completed,
Withdrawn,
}
/// Pool analytics data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolAnalytics {
pub price_history: Vec<PricePoint>,
pub volume_history: Vec<VolumePoint>,
pub liquidity_distribution: HashMap<String, Decimal>,
pub staking_distribution: HashMap<String, i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricePoint {
pub timestamp: DateTime<Utc>,
pub price: Decimal,
pub volume: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumePoint {
pub date: String,
pub volume: Decimal,
}

View File

@@ -0,0 +1,660 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::CustomType;
/// Generic product structure that can represent any marketplace item
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Product {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
#[index]
pub name: String,
pub category: ProductCategory,
pub description: String,
pub price: Price,
pub attributes: HashMap<String, ProductAttribute>, // Generic attributes
pub provider_base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub provider_name: String,
pub availability: ProductAvailability,
pub metadata: ProductMetadata, // Extensible metadata
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Price {
pub base_amount: Decimal,
pub currency: u32,
}
/// Configurable product categories
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProductCategory {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
pub name: String,
pub display_name: String,
pub description: String,
pub attribute_schema: Vec<AttributeDefinition>, // Defines allowed attributes
pub parent_category: Option<String>,
pub is_active: bool,
}
/// Generic attribute system for any product type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProductAttribute {
pub key: String,
pub value: serde_json::Value,
pub attribute_type: AttributeType,
pub is_searchable: bool,
pub is_filterable: bool,
pub display_order: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AttributeType {
Text,
Number,
SliceConfiguration,
Boolean,
Select(Vec<String>), // Predefined options
MultiSelect(Vec<String>),
Range { min: f64, max: f64 },
Custom(String), // For marketplace-specific types
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AttributeDefinition {
pub key: String,
pub name: String,
pub attribute_type: AttributeType,
pub is_required: bool,
pub is_searchable: bool,
pub is_filterable: bool,
pub validation_rules: Vec<ValidationRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ValidationRule {
MinLength(usize),
MaxLength(usize),
MinValue(f64),
MaxValue(f64),
Pattern(String),
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProductAvailability {
Available,
Limited,
Unavailable,
PreOrder,
Custom(String), // For marketplace-specific availability states
}
impl Default for ProductAvailability {
fn default() -> Self {
ProductAvailability::Available
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProductVisibility {
Public,
Private,
Draft,
Archived,
}
impl Default for ProductVisibility {
fn default() -> Self {
ProductVisibility::Public
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ProductMetadata {
pub tags: Vec<String>,
pub location: Option<String>,
pub rating: Option<f32>,
pub review_count: u32,
pub featured: bool,
pub last_updated: chrono::DateTime<chrono::Utc>,
pub visibility: ProductVisibility,
pub seo_keywords: Vec<String>,
pub custom_fields: HashMap<String, serde_json::Value>,
}
/// Support for different pricing models
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PricingModel {
OneTime, // Single purchase
Recurring { interval: String }, // Subscription
UsageBased { unit: String }, // Pay per use
Tiered(Vec<PriceTier>), // Volume discounts
Custom(String), // Marketplace-specific
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceTier {
pub min_quantity: u32,
pub max_quantity: Option<u32>,
pub price_per_unit: Decimal,
pub discount_percentage: Option<f32>,
}
impl Product {
pub fn new(
name: String,
category: ProductCategory,
description: String,
price: Price,
provider_base_data: BaseModelData::new(),
// id: String - moved to base_data,
provider_name: String,
) -> Self {
Self {
base_data: BaseModelData::new(),
name,
category,
description,
price,
attributes: HashMap::default(),
provider_id,
provider_name,
availability: ProductAvailability::Available,
metadata: ProductMetadata {
tags: Vec::default(),
location: None,
rating: None,
review_count: 0,
featured: false,
last_updated: chrono::Utc::now(),
visibility: ProductVisibility::Public,
seo_keywords: Vec::new(),
custom_fields: HashMap::default(),
},
}
}
pub fn add_attribute(&mut self, key: String, value: serde_json::Value, attribute_type: AttributeType) {
let attribute = ProductAttribute {
key: key.clone(),
value,
attribute_type,
is_searchable: true,
is_filterable: true,
display_order: None,
};
self.attributes.insert(key, attribute);
self.base_data.modified_at = Utc::now().timestamp();
}
pub fn set_featured(&mut self, featured: bool) {
self.metadata.featured = featured;
self.base_data.modified_at = Utc::now().timestamp();
}
pub fn add_tag(&mut self, tag: String) {
if !self.metadata.tags.contains(&tag) {
self.metadata.tags.push(tag);
self.base_data.modified_at = Utc::now().timestamp();
}
}
pub fn set_rating(&mut self, rating: f32, review_count: u32) {
self.metadata.rating = Some(rating);
self.metadata.review_count = review_count;
self.base_data.modified_at = Utc::now().timestamp();
}
}
impl ProductCategory {
pub fn new() -> Self {
// id: String - moved to base_data, name: String, display_name: String, description: String) -> Self {
Self {
base_data: BaseModelData::new(),
name,
display_name,
description,
attribute_schema: Vec::default(),
parent_category: None,
is_active: true,
}
}
/// Add attribute definition to category schema
pub fn add_attribute_definition(&mut self, definition: AttributeDefinition) {
self.attribute_schema.push(definition);
}
}
impl Product {
/// Create a slice product from farmer configuration
pub fn create_slice_product(
base_data: BaseModelData::new(),
// id: String - moved to base_data,
farmer_name: String,
slice_name: String,
slice_config: SliceConfiguration,
price_per_hour: Decimal,
) -> Self {
let category = ProductCategory {
base_data: BaseModelData::new(),
// id: "compute_slices".to_string() - moved to base_data,
name: "Compute Slices".to_string(),
display_name: "Compute Slices".to_string(),
description: "Virtual compute resources".to_string(),
attribute_schema: Vec::new(),
parent_category: None,
is_active: true,
};
let price = Price {
base_amount: price_per_hour,
currency: 1, // USD currency ID
};
let mut product = Self::new(
base_data,
slice_name,
category,
format!("Compute slice with {} vCPU, {}GB RAM, {}GB storage",
slice_config.cpu_cores, slice_config.memory_gb, slice_config.storage_gb),
price,
farmer_id,
farmer_name,
);
// Add slice-specific attributes
product.add_attribute(
"cpu_cores".to_string(),
serde_json::Value::Number(serde_json::Number::from(slice_config.cpu_cores)),
AttributeType::Number,
);
product.add_attribute(
"memory_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(slice_config.memory_gb)),
AttributeType::Number,
);
product.add_attribute(
"storage_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(slice_config.storage_gb)),
AttributeType::Number,
);
product.add_attribute(
"bandwidth_mbps".to_string(),
serde_json::Value::Number(serde_json::Number::from(slice_config.bandwidth_mbps)),
AttributeType::Number,
);
product.add_attribute(
"min_uptime_sla".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(slice_config.min_uptime_sla as f64).unwrap()),
AttributeType::Number,
);
product.add_attribute(
"public_ips".to_string(),
serde_json::Value::Number(serde_json::Number::from(slice_config.public_ips)),
AttributeType::Number,
);
if let Some(ref node_id) = slice_config.node_id {
product.add_attribute(
"node_id".to_string(),
serde_json::Value::String(node_id.clone()),
AttributeType::Text,
);
}
product.add_attribute(
"slice_type".to_string(),
serde_json::Value::String(format!("{:?}", slice_config.slice_type)),
AttributeType::Text,
);
// Add slice configuration as a complex attribute
product.add_attribute(
"slice_configuration".to_string(),
serde_json::to_value(&slice_config).unwrap(),
AttributeType::SliceConfiguration,
);
// Add relevant tags
product.add_tag("compute".to_string());
product.add_tag("slice".to_string());
product.add_tag(format!("{:?}", slice_config.slice_type).to_lowercase());
product
}
/// Check if this product is a slice
pub fn is_slice(&self) -> bool {
self.category.id == "compute_slices" ||
self.attributes.contains_key("slice_configuration")
}
/// Get slice configuration from product attributes
pub fn get_slice_configuration(&self) -> Option<SliceConfiguration> {
self.attributes.get("slice_configuration")
.and_then(|attr| serde_json::from_value(attr.value.clone()).ok())
}
/// Update slice configuration
pub fn update_slice_configuration(&mut self, config: SliceConfiguration) {
if self.is_slice() {
self.add_attribute(
"slice_configuration".to_string(),
serde_json::to_value(&config).unwrap(),
AttributeType::SliceConfiguration,
);
// Update individual attributes for searchability
self.add_attribute(
"cpu_cores".to_string(),
serde_json::Value::Number(serde_json::Number::from(config.cpu_cores)),
AttributeType::Number,
);
self.add_attribute(
"memory_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(config.memory_gb)),
AttributeType::Number,
);
self.add_attribute(
"storage_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(config.storage_gb)),
AttributeType::Number,
);
}
}
/// Check if slice fits within node capacity
pub fn slice_fits_in_node(&self, node_capacity: &crate::models::user::NodeCapacity) -> bool {
if let Some(config) = self.get_slice_configuration() {
config.cpu_cores <= node_capacity.cpu_cores &&
config.memory_gb <= node_capacity.memory_gb &&
config.storage_gb <= node_capacity.storage_gb &&
config.bandwidth_mbps <= node_capacity.bandwidth_mbps
} else {
false
}
}
/// Create a full node product from a FarmNode
pub fn create_full_node_product(
node: &crate::models::user::FarmNode,
farmer_email: &str,
farmer_name: &str,
) -> Self {
let category = ProductCategory {
base_data: BaseModelData::new(),
// id: "3nodes".to_string() - moved to base_data,
name: "3Nodes".to_string(),
display_name: "3Nodes".to_string(),
description: "Full node rentals".to_string(),
attribute_schema: Vec::new(),
parent_category: None,
is_active: true,
};
let price = Price {
base_amount: node.rental_options
.as_ref()
.and_then(|opts| opts.full_node_pricing.as_ref())
.map(|pricing| pricing.monthly)
.unwrap_or_else(|| Decimal::from(200)), // Default price
currency: 1, // USD currency ID
};
let mut product = Product {
base_data: BaseModelData::new(),
name: format!("Full Node: {}", node.name),
category,
description: format!(
"Exclusive access to {} with {} CPU cores, {}GB RAM, {}GB storage in {}",
node.name, node.capacity.cpu_cores, node.capacity.memory_gb,
node.capacity.storage_gb, node.location
),
price,
attributes: HashMap::new(),
provider_base_data: BaseModelData::new(),
// id: farmer_email.to_string() - moved to base_data,
provider_name: farmer_name.to_string(),
availability: match node.availability_status {
crate::models::user::NodeAvailabilityStatus::Available => ProductAvailability::Available,
crate::models::user::NodeAvailabilityStatus::PartiallyRented => ProductAvailability::Limited,
_ => ProductAvailability::Unavailable,
},
metadata: ProductMetadata {
tags: vec!["full-node".to_string(), "exclusive".to_string(), node.region.clone()],
location: Some(node.location.clone()),
rating: None,
review_count: 0,
featured: false,
last_updated: chrono::Utc::now(),
visibility: ProductVisibility::Public,
seo_keywords: Vec::new(),
custom_fields: HashMap::new(),
},
};
// Add node-specific attributes
product.add_attribute(
"node_id".to_string(),
serde_json::Value::String(node.id.clone()),
AttributeType::Text,
);
product.add_attribute(
"rental_type".to_string(),
serde_json::Value::String("full_node".to_string()),
AttributeType::Text,
);
product.add_attribute(
"cpu_cores".to_string(),
serde_json::Value::Number(serde_json::Number::from(node.capacity.cpu_cores)),
AttributeType::Number,
);
product.add_attribute(
"memory_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(node.capacity.memory_gb)),
AttributeType::Number,
);
product.add_attribute(
"storage_gb".to_string(),
serde_json::Value::Number(serde_json::Number::from(node.capacity.storage_gb)),
AttributeType::Number,
);
product.add_attribute(
"bandwidth_mbps".to_string(),
serde_json::Value::Number(serde_json::Number::from(node.capacity.bandwidth_mbps)),
AttributeType::Number,
);
product.add_attribute(
"location".to_string(),
serde_json::Value::String(node.location.clone()),
AttributeType::Text,
);
product.add_attribute(
"uptime_percentage".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(node.uptime_percentage as f64).unwrap_or_else(|| serde_json::Number::from(0))),
AttributeType::Number,
);
product.add_attribute(
"health_score".to_string(),
serde_json::Value::Number(serde_json::Number::from_f64(node.health_score as f64).unwrap_or_else(|| serde_json::Number::from(0))),
AttributeType::Number,
);
product
}
/// Check if this product represents a full node
pub fn is_full_node(&self) -> bool {
self.attributes.get("rental_type")
.and_then(|attr| attr.value.as_str())
.map(|s| s == "full_node")
.unwrap_or(false)
}
/// Get the node ID if this is a node product
pub fn get_node_id(&self) -> Option<String> {
self.attributes.get("node_id")
.and_then(|attr| attr.value.as_str())
.map(|s| s.to_string())
}
}
impl ProductCategory {
pub fn set_parent_category(&mut self, parent_id: String) {
self.parent_category = Some(parent_id);
}
}
impl AttributeDefinition {
pub fn new(
key: String,
name: String,
attribute_type: AttributeType,
is_required: bool,
) -> Self {
Self {
key,
name,
attribute_type,
is_required,
is_searchable: true,
is_filterable: true,
validation_rules: Vec::default(),
}
}
pub fn add_validation_rule(&mut self, rule: ValidationRule) {
self.validation_rules.push(rule);
}
}
#[derive(Default)]
pub struct ProductBuilder {
base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
name: Option<String>,
category_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
description: Option<String>,
base_price: Option<Decimal>,
base_currency: Option<String>,
attributes: HashMap<String, ProductAttribute>,
provider_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
provider_name: Option<String>,
availability: Option<ProductAvailability>,
metadata: Option<ProductMetadata>,
// created_at: Option<DateTime<Utc>> - moved to base_data,
// updated_at: Option<DateTime<Utc>> - moved to base_data,
}
impl ProductBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.base_data.id = Some(id.into());
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn category_id(mut self, category_id: impl Into<String>) -> Self {
self.category_id = Some(category_id.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn base_price(mut self, price: Decimal) -> Self {
self.base_price = Some(price);
self
}
pub fn base_currency(mut self, currency: impl Into<String>) -> Self {
self.base_currency = Some(currency.into());
self
}
pub fn add_attribute(mut self, key: impl Into<String>, attribute: ProductAttribute) -> Self {
self.attributes.insert(key.into(), attribute);
self
}
pub fn provider_id(mut self, provider_id: impl Into<String>) -> Self {
self.provider_id = Some(provider_id.into());
self
}
pub fn provider_name(mut self, provider_name: impl Into<String>) -> Self {
self.provider_name = Some(provider_name.into());
self
}
pub fn availability(mut self, availability: ProductAvailability) -> Self {
self.availability = Some(availability);
self
}
pub fn metadata(mut self, metadata: ProductMetadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn build(self) -> Result<Product, String> {
let now = Utc::now();
Ok(Product {
base_data: BaseModelData::new(),
// id: self.base_data.id.ok_or("id is required")? - moved to base_data,
name: self.name.ok_or("name is required")?,
category_base_data: BaseModelData::new(),
// id: self.category_id.ok_or("category_id is required")? - moved to base_data,
description: self.description.unwrap_or_default(),
base_price: self.base_price.ok_or("base_price is required")?,
base_currency: self.base_currency.unwrap_or_else(|| "USD".to_string()),
attributes: self.attributes,
provider_base_data: BaseModelData::new(),
// id: self.provider_id.ok_or("provider_id is required")? - moved to base_data,
provider_name: self.provider_name.ok_or("provider_name is required")?,
availability: self.availability.unwrap_or_default(),
metadata: self.metadata.unwrap_or_default(),
// created_at: self.base_data.created_at.unwrap_or(now) - moved to base_data,
// updated_at: self.base_data.updated_at.unwrap_or(now) - moved to base_data,
})
}
}
impl Product {
pub fn builder() -> ProductBuilder {
ProductBuilder::new()
}
}

View File

@@ -0,0 +1,297 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize, Deserializer};
use rust_decimal::Decimal;
use std::str::FromStr;
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
/// Service Provider-specific data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceProviderData {
pub active_services: i32,
pub total_clients: i32,
pub monthly_revenue_usd: i32,
pub total_revenue_usd: i32,
pub service_rating: f32,
pub services: Vec<Service>,
pub client_requests: Vec<ServiceRequest>,
pub revenue_history: Vec<RevenueRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Service {
pub base_data: BaseModelData::new(),
// id: String - moved to base_data,
pub name: String,
pub category: String,
pub description: String,
pub price_per_hour_usd: i32,
pub status: String,
pub clients: i32,
pub rating: f32,
pub total_hours: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceRequest {
/// Base model data (includes id, created_at, updated_at)
pub base_data: BaseModelData,
pub client_name: String,
pub service_name: String,
pub status: String,
pub requested_date: String,
pub estimated_hours: i32,
pub budget: i32,
pub priority: String,
#[serde(default)]
pub progress: Option<i32>,
#[serde(default)]
pub completed_date: Option<String>,
#[serde(default)]
pub client_email: Option<String>,
#[serde(default)]
pub client_phone: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub created_date: Option<String>,
}
/// Service booking record for customers who purchase services
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceBooking {
pub base_data: BaseModelData::new(),
// id: String - moved to base_data, // Same as ServiceRequest.id for cross-reference
pub service_base_data: BaseModelData::new(),
// id: String - moved to base_data, // Reference to original service
pub service_name: String,
pub provider_email: String, // Who provides the service
pub customer_email: String, // Who booked the service
pub budget: i32,
pub estimated_hours: i32,
pub status: String, // "Pending", "In Progress", "Completed"
pub requested_date: String,
pub priority: String,
pub description: Option<String>,
pub booking_date: String, // When customer booked
pub client_phone: Option<String>,
pub progress: Option<i32>,
pub completed_date: Option<String>,
}
/// Customer Service-specific data (for users who book services)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomerServiceData {
pub active_bookings: i32,
pub completed_bookings: i32,
pub total_spent: i32,
pub monthly_spending: i32,
pub average_rating_given: f32,
pub service_bookings: Vec<ServiceBooking>,
pub favorite_providers: Vec<String>,
pub spending_history: Vec<SpendingRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpendingRecord {
pub date: String,
pub amount: i32,
pub service_name: String,
pub provider_name: String,
}
#[derive(Default)]
pub struct ServiceBookingBuilder {
base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
service_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
service_name: Option<String>,
provider_email: Option<String>,
customer_email: Option<String>,
budget: Option<i32>,
estimated_hours: Option<i32>,
status: Option<String>,
requested_date: Option<String>,
priority: Option<String>,
description: Option<String>,
booking_date: Option<String>,
}
impl ServiceBookingBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn id(mut self) -> Self{
self.base_data.id = Some(id.to_string());
self
}
pub fn service_id(mut self, service_id: &str, name: &str) -> Self{
self.service_id = Some(service_id.to_string());
self
}
pub fn service_name(mut self, service_name: &str) -> Self {
self.service_name = Some(service_name.to_string());
self
}
pub fn provider_email(mut self, provider_email: &str) -> Self {
self.provider_email = Some(provider_email.to_string());
self
}
pub fn customer_email(mut self, customer_email: &str) -> Self {
self.customer_email = Some(customer_email.to_string());
self
}
pub fn budget(mut self, budget: i32) -> Self {
self.budget = Some(budget);
self
}
pub fn estimated_hours(mut self, hours: i32) -> Self {
self.estimated_hours = Some(hours);
self
}
pub fn status(mut self, status: &str) -> Self {
self.status = Some(status.to_string());
self
}
pub fn requested_date(mut self, date: &str) -> Self {
self.requested_date = Some(date.to_string());
self
}
pub fn priority(mut self, priority: &str) -> Self {
self.priority = Some(priority.to_string());
self
}
pub fn description(mut self, description: Option<String>) -> Self {
self.description = description;
self
}
pub fn booking_date(mut self, date: &str) -> Self {
self.booking_date = Some(date.to_string());
self
}
pub fn build(self) -> Result<ServiceBooking, String> {
Ok(ServiceBooking {
base_data: BaseModelData::new(),
// id: self.base_data.id.ok_or("ID is required")? - moved to base_data,
service_base_data: BaseModelData::new(),
// id: self.service_id.ok_or("Service ID is required")? - moved to base_data,
service_name: self.service_name.ok_or("Service name is required")?,
provider_email: self.provider_email.ok_or("Provider email is required")?,
customer_email: self.customer_email.ok_or("Customer email is required")?,
budget: self.budget.unwrap_or(0),
estimated_hours: self.estimated_hours.unwrap_or(0),
status: self.status.unwrap_or_else(|| "Pending".to_string()),
requested_date: self.requested_date.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()),
priority: self.priority.unwrap_or_else(|| "Medium".to_string()),
description: self.description,
booking_date: self.booking_date.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()),
client_phone: None,
progress: None,
completed_date: None,
})
}
}
impl ServiceBooking {
pub fn builder() -> ServiceBookingBuilder {
ServiceBookingBuilder::new()
}
}
// =============================================================================
// CUSTOMER SERVICE DATA BUILDER
// =============================================================================
#[derive(Default)]
pub struct CustomerServiceDataBuilder {
active_bookings: Option<i32>,
completed_bookings: Option<i32>,
total_spent: Option<i32>,
monthly_spending: Option<i32>,
average_rating_given: Option<f32>,
service_bookings: Option<Vec<crate::models::user::ServiceBooking>>,
favorite_providers: Option<Vec<String>>,
spending_history: Option<Vec<crate::models::user::SpendingRecord>>,
}
impl CustomerServiceDataBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn active_bookings(mut self, count: i32) -> Self {
self.active_bookings = Some(count);
self
}
pub fn completed_bookings(mut self, count: i32) -> Self {
self.completed_bookings = Some(count);
self
}
pub fn total_spent(mut self, amount: i32) -> Self {
self.total_spent = Some(amount);
self
}
pub fn monthly_spending(mut self, amount: i32) -> Self {
self.monthly_spending = Some(amount);
self
}
pub fn average_rating_given(mut self, rating: f32) -> Self {
self.average_rating_given = Some(rating);
self
}
pub fn service_bookings(mut self, bookings: Vec<crate::models::user::ServiceBooking>) -> Self {
self.service_bookings = Some(bookings);
self
}
pub fn favorite_providers(mut self, providers: Vec<String>) -> Self {
self.favorite_providers = Some(providers);
self
}
pub fn spending_history(mut self, history: Vec<crate::models::user::SpendingRecord>) -> Self {
self.spending_history = Some(history);
self
}
pub fn build(self) -> Result<crate::models::user::CustomerServiceData, String> {
Ok(crate::models::user::CustomerServiceData {
active_bookings: self.active_bookings.unwrap_or(0),
completed_bookings: self.completed_bookings.unwrap_or(0),
total_spent: self.total_spent.unwrap_or(0),
monthly_spending: self.monthly_spending.unwrap_or(0),
average_rating_given: self.average_rating_given.unwrap_or(0.0),
service_bookings: self.service_bookings.unwrap_or_default(),
favorite_providers: self.favorite_providers.unwrap_or_default(),
spending_history: self.spending_history.unwrap_or_default(),
})
}
}
impl crate::models::user::CustomerServiceData {
pub fn builder() -> CustomerServiceDataBuilder {
CustomerServiceDataBuilder::new()
}
}

View File

@@ -0,0 +1,200 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::collections::HashMap;
use heromodels_core::BaseModelData;
use crate::models::tfmarketplace::user::ResourceUtilization;
/// Slice configuration data structure for product attributes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceConfiguration {
pub cpu_cores: i32,
pub memory_gb: i32,
pub storage_gb: i32,
pub bandwidth_mbps: i32,
pub min_uptime_sla: f32,
pub public_ips: i32,
pub node_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
pub slice_type: SliceType,
#[serde(default)]
pub pricing: SlicePricing,
}
/// Enhanced pricing structure for slices with multiple time periods
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlicePricing {
pub hourly: Decimal,
pub daily: Decimal,
pub monthly: Decimal,
pub yearly: Decimal,
}
impl Default for SlicePricing {
fn default() -> Self {
Self {
hourly: Decimal::ZERO,
daily: Decimal::ZERO,
monthly: Decimal::ZERO,
yearly: Decimal::ZERO,
}
}
}
impl SlicePricing {
/// Create pricing from hourly rate with automatic calculation
pub fn from_hourly(hourly_rate: Decimal, daily_discount: f32, monthly_discount: f32, yearly_discount: f32) -> Self {
let base_daily = hourly_rate * Decimal::from(24);
let base_monthly = hourly_rate * Decimal::from(24 * 30);
let base_yearly = hourly_rate * Decimal::from(24 * 365);
Self {
hourly: hourly_rate,
daily: base_daily * Decimal::try_from(1.0 - daily_discount / 100.0).unwrap_or(Decimal::ONE),
monthly: base_monthly * Decimal::try_from(1.0 - monthly_discount / 100.0).unwrap_or(Decimal::ONE),
yearly: base_yearly * Decimal::try_from(1.0 - yearly_discount / 100.0).unwrap_or(Decimal::ONE),
}
}
/// Calculate savings compared to hourly rate
pub fn calculate_savings(&self) -> (Decimal, Decimal, Decimal) {
let hourly_equivalent_daily = self.hourly * Decimal::from(24);
let hourly_equivalent_monthly = self.hourly * Decimal::from(24 * 30);
let hourly_equivalent_yearly = self.hourly * Decimal::from(24 * 365);
let daily_savings = hourly_equivalent_daily - self.daily;
let monthly_savings = hourly_equivalent_monthly - self.monthly;
let yearly_savings = hourly_equivalent_yearly - self.yearly;
(daily_savings, monthly_savings, yearly_savings)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SliceType {
Basic,
Standard,
Premium,
Custom,
}
#[derive(Default)]
pub struct SliceProductBuilder {
farmer_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
farmer_name: Option<String>,
slice_name: Option<String>,
cpu_cores: Option<i32>,
memory_gb: Option<i32>,
storage_gb: Option<i32>,
bandwidth_mbps: Option<i32>,
min_uptime_sla: Option<f32>,
public_ips: Option<i32>,
node_base_data: BaseModelData::new(),
// id: Option<String> - moved to base_data,
slice_type: Option<crate::models::tfmarketplace::product::SliceType>,
price_per_hour: Option<rust_decimal::Decimal>,
}
impl SliceProductBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn farmer_id(mut self, farmer_id: &str, name: &str) -> Self{
self.farmer_id = Some(farmer_id.into());
self
}
pub fn farmer_name(mut self, farmer_name: impl Into<String>) -> Self {
self.farmer_name = Some(farmer_name.into());
self
}
pub fn slice_name(mut self, slice_name: impl Into<String>) -> Self {
self.slice_name = Some(slice_name.into());
self
}
pub fn cpu_cores(mut self, cpu_cores: i32) -> Self {
self.cpu_cores = Some(cpu_cores);
self
}
pub fn memory_gb(mut self, memory_gb: i32) -> Self {
self.memory_gb = Some(memory_gb);
self
}
pub fn storage_gb(mut self, storage_gb: i32) -> Self {
self.storage_gb = Some(storage_gb);
self
}
pub fn bandwidth_mbps(mut self, bandwidth_mbps: i32) -> Self {
self.bandwidth_mbps = Some(bandwidth_mbps);
self
}
pub fn min_uptime_sla(mut self, min_uptime_sla: f32) -> Self {
self.min_uptime_sla = Some(min_uptime_sla);
self
}
pub fn public_ips(mut self, public_ips: i32) -> Self {
self.public_ips = Some(public_ips);
self
}
pub fn node_id(mut self, node_id: &str, name: &str) -> Self{
self.node_id = Some(node_id.into());
self
}
pub fn slice_type(mut self, slice_type: crate::models::tfmarketplace::product::SliceType) -> Self {
self.slice_type = Some(slice_type);
self
}
pub fn price_per_hour(mut self, price_per_hour: rust_decimal::Decimal) -> Self {
self.price_per_hour = Some(price_per_hour);
self
}
pub fn build(self) -> Result<crate::models::tfmarketplace::product::Product, String> {
let farmer_id = self.farmer_id.ok_or("farmer_id is required")?;
let farmer_name = self.farmer_name.ok_or("farmer_name is required")?;
let slice_name = self.slice_name.ok_or("slice_name is required")?;
let cpu_cores = self.cpu_cores.ok_or("cpu_cores is required")?;
let memory_gb = self.memory_gb.ok_or("memory_gb is required")?;
let storage_gb = self.storage_gb.ok_or("storage_gb is required")?;
let bandwidth_mbps = self.bandwidth_mbps.ok_or("bandwidth_mbps is required")?;
let price_per_hour = self.price_per_hour.ok_or("price_per_hour is required")?;
let slice_config = crate::models::tfmarketplace::product::SliceConfiguration {
cpu_cores,
memory_gb,
storage_gb,
bandwidth_mbps,
min_uptime_sla: self.min_uptime_sla.unwrap_or(99.0),
public_ips: self.public_ips.unwrap_or(0),
node_base_data: BaseModelData::new(),
// id: self.node_id - moved to base_data,
slice_type: self.slice_type.unwrap_or(crate::models::tfmarketplace::product::SliceType::Basic),
pricing: crate::models::tfmarketplace::product::SlicePricing::from_hourly(
price_per_hour,
5.0, // 5% daily discount
15.0, // 15% monthly discount
25.0 // 25% yearly discount
),
};
Ok(crate::models::tfmarketplace::product::Product::create_slice_product(
farmer_id,
farmer_name,
slice_name,
slice_config,
price_per_hour,
))
}
}

File diff suppressed because it is too large Load Diff