Compare commits
5 Commits
58ed59cd12
...
main
Author | SHA1 | Date | |
---|---|---|---|
97c24d146b | |||
6bff52e8b7 | |||
7a999b7b6e | |||
095a4d0c69 | |||
|
cedea2f305 |
274
Cargo.lock
generated
274
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
@@ -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"] }
|
||||
|
@@ -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 ---");
|
||||
|
148
heromodels/src/models/access/rhai.rs
Normal file
148
heromodels/src/models/access/rhai.rs
Normal 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());
|
||||
}
|
422
heromodels/src/models/biz/rhai.rs
Normal file
422
heromodels/src/models/biz/rhai.rs
Normal 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());
|
||||
}
|
246
heromodels/src/models/calendar/rhai.rs
Normal file
246
heromodels/src/models/calendar/rhai.rs
Normal 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());
|
||||
}
|
@@ -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());
|
||||
}
|
||||
|
232
heromodels/src/models/contact/rhai.rs
Normal file
232
heromodels/src/models/contact/rhai.rs
Normal 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());
|
||||
}
|
86
heromodels/src/models/core/rhai.rs
Normal file
86
heromodels/src/models/core/rhai.rs
Normal 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());
|
||||
}
|
80
heromodels/src/models/finance/rhai.rs
Normal file
80
heromodels/src/models/finance/rhai.rs
Normal 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());
|
||||
}
|
16
heromodels/src/models/grid4/mod.rs
Normal file
16
heromodels/src/models/grid4/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
pub mod node;
|
||||
|
||||
pub use node::{
|
||||
Node,
|
||||
DeviceInfo,
|
||||
StorageDevice,
|
||||
MemoryDevice,
|
||||
CPUDevice,
|
||||
GPUDevice,
|
||||
NetworkDevice,
|
||||
NodeCapacity,
|
||||
ComputeSlice,
|
||||
StorageSlice,
|
||||
PricingPolicy,
|
||||
SLAPolicy,
|
||||
};
|
265
heromodels/src/models/grid4/node.rs
Normal file
265
heromodels/src/models/grid4/node.rs
Normal 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
|
||||
}
|
||||
}
|
194
heromodels/src/models/grid4/specs/README.md
Normal file
194
heromodels/src/models/grid4/specs/README.md
Normal file
@@ -0,0 +1,194 @@
|
||||
|
||||
# Grid4 Data Model
|
||||
|
||||
This module defines data models for nodes, groups, and slices in a cloud/grid infrastructure. Each root object is marked with `@[heap]` and can be indexed for efficient querying.
|
||||
|
||||
## Root Objects Overview
|
||||
|
||||
| Object | Description | Index Fields |
|
||||
| ----------- | --------------------------------------------- | ------------------------------ |
|
||||
| `Node` | Represents a single node in the grid | `id`, `nodegroupid`, `country` |
|
||||
| `NodeGroup` | Represents a group of nodes owned by a farmer | `id`, `farmerid` |
|
||||
|
||||
---
|
||||
|
||||
## Node
|
||||
|
||||
Represents a single node in the grid with slices, devices, and capacity.
|
||||
|
||||
| Field | Type | Description | Indexed |
|
||||
| --------------- | ---------------- | -------------------------------------------- | ------- |
|
||||
| `id` | `int` | Unique node ID | ✅ |
|
||||
| `nodegroupid` | `int` | ID of the owning node group | ✅ |
|
||||
| `uptime` | `int` | Uptime percentage (0-100) | ✅ |
|
||||
| `computeslices` | `[]ComputeSlice` | List of compute slices | ❌ |
|
||||
| `storageslices` | `[]StorageSlice` | List of storage slices | ❌ |
|
||||
| `devices` | `DeviceInfo` | Hardware device info (storage, memory, etc.) | ❌ |
|
||||
| `country` | `string` | 2-letter country code | ✅ |
|
||||
| `capacity` | `NodeCapacity` | Aggregated hardware capacity | ❌ |
|
||||
| `provisiontime` | `u32` | Provisioning time (simple/compatible format) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## NodeGroup
|
||||
|
||||
Represents a group of nodes owned by a farmer, with policies.
|
||||
|
||||
| Field | Type | Description | Indexed |
|
||||
| ------------------------------------- | --------------- | ---------------------------------------------- | ------- |
|
||||
| `id` | `u32` | Unique group ID | ✅ |
|
||||
| `farmerid` | `u32` | Farmer/user ID | ✅ |
|
||||
| `secret` | `string` | Encrypted secret for booting nodes | ❌ |
|
||||
| `description` | `string` | Group description | ❌ |
|
||||
| `slapolicy` | `SLAPolicy` | SLA policy details | ❌ |
|
||||
| `pricingpolicy` | `PricingPolicy` | Pricing policy details | ❌ |
|
||||
| `compute_slice_normalized_pricing_cc` | `f64` | Pricing per 2GB compute slice in cloud credits | ❌ |
|
||||
| `storage_slice_normalized_pricing_cc` | `f64` | Pricing per 1GB storage slice in cloud credits | ❌ |
|
||||
| `reputation` | `int` | Reputation (0-100) | ✅ |
|
||||
| `uptime` | `int` | Uptime (0-100) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ComputeSlice
|
||||
|
||||
Represents a compute slice (e.g., 1GB memory unit).
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------------------- | --------------- | -------------------------------- |
|
||||
| `nodeid` | `u32` | Owning node ID |
|
||||
| `id` | `int` | Slice ID in node |
|
||||
| `mem_gb` | `f64` | Memory in GB |
|
||||
| `storage_gb` | `f64` | Storage in GB |
|
||||
| `passmark` | `int` | Passmark score |
|
||||
| `vcores` | `int` | Virtual cores |
|
||||
| `cpu_oversubscription` | `int` | CPU oversubscription ratio |
|
||||
| `storage_oversubscription` | `int` | Storage oversubscription ratio |
|
||||
| `price_range` | `[]f64` | Price range [min, max] |
|
||||
| `gpus` | `u8` | Number of GPUs |
|
||||
| `price_cc` | `f64` | Price per slice in cloud credits |
|
||||
| `pricing_policy` | `PricingPolicy` | Pricing policy |
|
||||
| `sla_policy` | `SLAPolicy` | SLA policy |
|
||||
|
||||
---
|
||||
|
||||
## StorageSlice
|
||||
|
||||
Represents a 1GB storage slice.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------------- | --------------- | -------------------------------- |
|
||||
| `nodeid` | `u32` | Owning node ID |
|
||||
| `id` | `int` | Slice ID in node |
|
||||
| `price_cc` | `f64` | Price per slice in cloud credits |
|
||||
| `pricing_policy` | `PricingPolicy` | Pricing policy |
|
||||
| `sla_policy` | `SLAPolicy` | SLA policy |
|
||||
|
||||
---
|
||||
|
||||
## DeviceInfo
|
||||
|
||||
Hardware device information for a node.
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------- | ----------------- | ----------------------- |
|
||||
| `vendor` | `string` | Vendor of the node |
|
||||
| `storage` | `[]StorageDevice` | List of storage devices |
|
||||
| `memory` | `[]MemoryDevice` | List of memory devices |
|
||||
| `cpu` | `[]CPUDevice` | List of CPU devices |
|
||||
| `gpu` | `[]GPUDevice` | List of GPU devices |
|
||||
| `network` | `[]NetworkDevice` | List of network devices |
|
||||
|
||||
---
|
||||
|
||||
## StorageDevice
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | -------- | --------------------- |
|
||||
| `id` | `string` | Unique ID for device |
|
||||
| `size_gb` | `f64` | Size in GB |
|
||||
| `description` | `string` | Description of device |
|
||||
|
||||
---
|
||||
|
||||
## MemoryDevice
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | -------- | --------------------- |
|
||||
| `id` | `string` | Unique ID for device |
|
||||
| `size_gb` | `f64` | Size in GB |
|
||||
| `description` | `string` | Description of device |
|
||||
|
||||
---
|
||||
|
||||
## CPUDevice
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | -------- | ------------------------ |
|
||||
| `id` | `string` | Unique ID for device |
|
||||
| `cores` | `int` | Number of CPU cores |
|
||||
| `passmark` | `int` | Passmark benchmark score |
|
||||
| `description` | `string` | Description of device |
|
||||
| `cpu_brand` | `string` | Brand of the CPU |
|
||||
| `cpu_version` | `string` | Version of the CPU |
|
||||
|
||||
---
|
||||
|
||||
## GPUDevice
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | -------- | --------------------- |
|
||||
| `id` | `string` | Unique ID for device |
|
||||
| `cores` | `int` | Number of GPU cores |
|
||||
| `memory_gb` | `f64` | GPU memory in GB |
|
||||
| `description` | `string` | Description of device |
|
||||
| `gpu_brand` | `string` | Brand of the GPU |
|
||||
| `gpu_version` | `string` | Version of the GPU |
|
||||
|
||||
---
|
||||
|
||||
## NetworkDevice
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | -------- | --------------------- |
|
||||
| `id` | `string` | Unique ID for device |
|
||||
| `speed_mbps` | `int` | Network speed in Mbps |
|
||||
| `description` | `string` | Description of device |
|
||||
|
||||
---
|
||||
|
||||
## NodeCapacity
|
||||
|
||||
Aggregated hardware capacity for a node.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------ | ----- | ---------------------- |
|
||||
| `storage_gb` | `f64` | Total storage in GB |
|
||||
| `mem_gb` | `f64` | Total memory in GB |
|
||||
| `mem_gb_gpu` | `f64` | Total GPU memory in GB |
|
||||
| `passmark` | `int` | Total passmark score |
|
||||
| `vcores` | `int` | Total virtual cores |
|
||||
|
||||
---
|
||||
|
||||
## SLAPolicy
|
||||
|
||||
Service Level Agreement policy for slices or node groups.
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------------- | ----- | --------------------------------------- |
|
||||
| `sla_uptime` | `int` | Required uptime % (e.g., 90) |
|
||||
| `sla_bandwidth_mbit` | `int` | Guaranteed bandwidth in Mbps (0 = none) |
|
||||
| `sla_penalty` | `int` | Penalty % if SLA is breached (0-100) |
|
||||
|
||||
---
|
||||
|
||||
## PricingPolicy
|
||||
|
||||
Pricing policy for slices or node groups.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------------------------- | ------- | --------------------------------------------------------- |
|
||||
| `marketplace_year_discounts` | `[]int` | Discounts for 1Y, 2Y, 3Y prepaid usage (e.g. [30,40,50]) |
|
||||
| `volume_discounts` | `[]int` | Volume discounts based on purchase size (e.g. [10,20,30]) |
|
||||
|
||||
|
37
heromodels/src/models/grid4/specs/model_bid.v
Normal file
37
heromodels/src/models/grid4/specs/model_bid.v
Normal file
@@ -0,0 +1,37 @@
|
||||
module datamodel
|
||||
|
||||
// I can bid for infra, and optionally get accepted
|
||||
@[heap]
|
||||
pub struct Bid {
|
||||
pub mut:
|
||||
id u32
|
||||
customer_id u32 // links back to customer for this capacity (user on ledger)
|
||||
compute_slices_nr int // nr of slices I need in 1 machine
|
||||
compute_slice_price f64 // price per 1 GB slice I want to accept
|
||||
storage_slices_nr int
|
||||
storage_slice_price f64 // price per 1 GB storage slice I want to accept
|
||||
storage_slices_nr int
|
||||
status BidStatus
|
||||
obligation bool // if obligation then will be charged and money needs to be in escrow, otherwise its an intent
|
||||
start_date u32 // epoch
|
||||
end_date u32
|
||||
signature_user string // signature as done by a user/consumer to validate their identity and intent
|
||||
billing_period BillingPeriod
|
||||
}
|
||||
|
||||
pub enum BidStatus {
|
||||
pending
|
||||
confirmed
|
||||
assigned
|
||||
cancelled
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
pub enum BillingPeriod {
|
||||
hourly
|
||||
monthly
|
||||
yearly
|
||||
biannually
|
||||
triannually
|
||||
}
|
52
heromodels/src/models/grid4/specs/model_contract.v
Normal file
52
heromodels/src/models/grid4/specs/model_contract.v
Normal file
@@ -0,0 +1,52 @@
|
||||
module datamodel
|
||||
|
||||
// I can bid for infra, and optionally get accepted
|
||||
@[heap]
|
||||
pub struct Contract {
|
||||
pub mut:
|
||||
id u32
|
||||
customer_id u32 // links back to customer for this capacity (user on ledger)
|
||||
compute_slices []ComputeSliceProvisioned
|
||||
storage_slices []StorageSliceProvisioned
|
||||
compute_slice_price f64 // price per 1 GB agreed upon
|
||||
storage_slice_price f64 // price per 1 GB agreed upon
|
||||
network_slice_price f64 // price per 1 GB agreed upon (transfer)
|
||||
status ContractStatus
|
||||
start_date u32 // epoch
|
||||
end_date u32
|
||||
signature_user string // signature as done by a user/consumer to validate their identity and intent
|
||||
signature_hoster string // signature as done by the hoster
|
||||
billing_period BillingPeriod
|
||||
}
|
||||
|
||||
pub enum ConctractStatus {
|
||||
active
|
||||
cancelled
|
||||
error
|
||||
paused
|
||||
}
|
||||
|
||||
|
||||
// typically 1GB of memory, but can be adjusted based based on size of machine
|
||||
pub struct ComputeSliceProvisioned {
|
||||
pub mut:
|
||||
node_id u32
|
||||
id u16 // the id of the slice in the node
|
||||
mem_gb f64
|
||||
storage_gb f64
|
||||
passmark int
|
||||
vcores int
|
||||
cpu_oversubscription int
|
||||
tags string
|
||||
}
|
||||
|
||||
// 1GB of storage
|
||||
pub struct StorageSliceProvisioned {
|
||||
pub mut:
|
||||
node_id u32
|
||||
id u16 // the id of the slice in the node, are tracked in the node itself
|
||||
storage_size_gb int
|
||||
tags string
|
||||
}
|
||||
|
||||
|
104
heromodels/src/models/grid4/specs/model_node.v
Normal file
104
heromodels/src/models/grid4/specs/model_node.v
Normal file
@@ -0,0 +1,104 @@
|
||||
module datamodel
|
||||
|
||||
//ACCESS ONLY TF
|
||||
|
||||
@[heap]
|
||||
pub struct Node {
|
||||
pub mut:
|
||||
id int
|
||||
nodegroupid int
|
||||
uptime int // 0..100
|
||||
computeslices []ComputeSlice
|
||||
storageslices []StorageSlice
|
||||
devices DeviceInfo
|
||||
country string // 2 letter code as specified in lib/data/countries/data/countryInfo.txt, use that library for validation
|
||||
capacity NodeCapacity // Hardware capacity details
|
||||
birthtime u32 // first time node was active
|
||||
pubkey string
|
||||
signature_node string // signature done on node to validate pubkey with privkey
|
||||
signature_farmer string // signature as done by farmers to validate their identity
|
||||
}
|
||||
|
||||
pub struct DeviceInfo {
|
||||
pub mut:
|
||||
vendor string
|
||||
storage []StorageDevice
|
||||
memory []MemoryDevice
|
||||
cpu []CPUDevice
|
||||
gpu []GPUDevice
|
||||
network []NetworkDevice
|
||||
}
|
||||
|
||||
pub struct StorageDevice {
|
||||
pub mut:
|
||||
id string // can be used in node
|
||||
size_gb f64 // Size of the storage device in gigabytes
|
||||
description string // Description of the storage device
|
||||
}
|
||||
|
||||
pub struct MemoryDevice {
|
||||
pub mut:
|
||||
id string // can be used in node
|
||||
size_gb f64 // Size of the memory device in gigabytes
|
||||
description string // Description of the memory device
|
||||
}
|
||||
|
||||
pub struct CPUDevice {
|
||||
pub mut:
|
||||
id string // can be used in node
|
||||
cores int // Number of CPU cores
|
||||
passmark int
|
||||
description string // Description of the CPU
|
||||
cpu_brand string // Brand of the CPU
|
||||
cpu_version string // Version of the CPU
|
||||
}
|
||||
|
||||
pub struct GPUDevice {
|
||||
pub mut:
|
||||
id string // can be used in node
|
||||
cores int // Number of GPU cores
|
||||
memory_gb f64 // Size of the GPU memory in gigabytes
|
||||
description string // Description of the GPU
|
||||
gpu_brand string
|
||||
gpu_version string
|
||||
}
|
||||
|
||||
pub struct NetworkDevice {
|
||||
pub mut:
|
||||
id string // can be used in node
|
||||
speed_mbps int // Network speed in Mbps
|
||||
description string // Description of the network device
|
||||
}
|
||||
|
||||
// NodeCapacity represents the hardware capacity details of a node.
|
||||
pub struct NodeCapacity {
|
||||
pub mut:
|
||||
storage_gb f64 // Total storage in gigabytes
|
||||
mem_gb f64 // Total memory in gigabytes
|
||||
mem_gb_gpu f64 // Total GPU memory in gigabytes
|
||||
passmark int // Passmark score for the node
|
||||
vcores int // Total virtual cores
|
||||
}
|
||||
|
||||
// typically 1GB of memory, but can be adjusted based based on size of machine
|
||||
pub struct ComputeSlice {
|
||||
pub mut:
|
||||
u16 int // the id of the slice in the node
|
||||
mem_gb f64
|
||||
storage_gb f64
|
||||
passmark int
|
||||
vcores int
|
||||
cpu_oversubscription int
|
||||
storage_oversubscription int
|
||||
gpus u8 // nr of GPU's see node to know what GPU's are
|
||||
}
|
||||
|
||||
// 1GB of storage
|
||||
pub struct StorageSlice {
|
||||
pub mut:
|
||||
u16 int // the id of the slice in the node, are tracked in the node itself
|
||||
}
|
||||
|
||||
fn (mut n Node) check() ! {
|
||||
// todo calculate NodeCapacity out of the devices on the Node
|
||||
}
|
33
heromodels/src/models/grid4/specs/model_nodegroup.v
Normal file
33
heromodels/src/models/grid4/specs/model_nodegroup.v
Normal file
@@ -0,0 +1,33 @@
|
||||
module datamodel
|
||||
|
||||
// is a root object, is the only obj farmer needs to configure in the UI, this defines how slices will be created
|
||||
@[heap]
|
||||
pub struct NodeGroup {
|
||||
pub mut:
|
||||
id u32
|
||||
farmerid u32 // link back to farmer who owns the nodegroup, is a user?
|
||||
secret string // only visible by farmer, in future encrypted, used to boot a node
|
||||
description string
|
||||
slapolicy SLAPolicy
|
||||
pricingpolicy PricingPolicy
|
||||
compute_slice_normalized_pricing_cc f64 // pricing in CC - cloud credit, per 2GB node slice
|
||||
storage_slice_normalized_pricing_cc f64 // pricing in CC - cloud credit, per 1GB storage slice
|
||||
signature_farmer string // signature as done by farmers to validate that they created this group
|
||||
}
|
||||
|
||||
pub struct SLAPolicy {
|
||||
pub mut:
|
||||
sla_uptime int // should +90
|
||||
sla_bandwidth_mbit int // minimal mbits we can expect avg over 1h per node, 0 means we don't guarantee
|
||||
sla_penalty int // 0-100, percent of money given back in relation to month if sla breached, e.g. 200 means we return 2 months worth of rev if sla missed
|
||||
}
|
||||
|
||||
pub struct PricingPolicy {
|
||||
pub mut:
|
||||
marketplace_year_discounts []int = [30, 40, 50] // e.g. 30,40,50 means if user has more CC in wallet than 1 year utilization on all his purchaes then this provider gives 30%, 2Y 40%, ...
|
||||
// volume_discounts []int = [10, 20, 30] // e.g. 10,20,30
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
19
heromodels/src/models/grid4/specs/model_reputation.v
Normal file
19
heromodels/src/models/grid4/specs/model_reputation.v
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
@[heap]
|
||||
pub struct NodeGroupReputation {
|
||||
pub mut:
|
||||
nodegroup_id u32
|
||||
reputation int = 50 // between 0 and 100, earned over time
|
||||
uptime int // between 0 and 100, set by system, farmer has no ability to set this
|
||||
nodes []NodeReputation
|
||||
}
|
||||
|
||||
pub struct NodeReputation {
|
||||
pub mut:
|
||||
node_id u32
|
||||
reputation int = 50 // between 0 and 100, earned over time
|
||||
uptime int // between 0 and 100, set by system, farmer has no ability to set this
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
|
156
heromodels/src/models/library/rhai.rs
Normal file
156
heromodels/src/models/library/rhai.rs
Normal 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());
|
||||
}
|
11
heromodels/src/models/location/address.rs
Normal file
11
heromodels/src/models/location/address.rs
Normal 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>,
|
||||
}
|
2
heromodels/src/models/location/mod.rs
Normal file
2
heromodels/src/models/location/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
// Export location models
|
||||
pub mod address;
|
@@ -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;
|
||||
|
27
heromodels/src/models/object/rhai.rs
Normal file
27
heromodels/src/models/object/rhai.rs
Normal 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");
|
||||
}
|
49
heromodels/src/models/payment/rhai.rs
Normal file
49
heromodels/src/models/payment/rhai.rs
Normal 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());
|
||||
}
|
345
specs/billingmanager_research/billingmanager.md
Normal file
345
specs/billingmanager_research/billingmanager.md
Normal file
@@ -0,0 +1,345 @@
|
||||
|
||||
### 2.1 Accounts
|
||||
|
||||
* **id**: `BIGINT` identity (non-negative), unique account id
|
||||
* **pubkey**: `BYTEA` unique public key for signing/encryption
|
||||
* **display\_name**: `TEXT` (optional)
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
### 2.2 Currencies
|
||||
|
||||
* **asset\_code**: `TEXT` PK (e.g., `USDC-ETH`, `EUR`, `LND`)
|
||||
* **name**: `TEXT`
|
||||
* **symbol**: `TEXT`
|
||||
* **decimals**: `INT` (default 2)
|
||||
|
||||
---
|
||||
|
||||
## 3) Services & Groups
|
||||
|
||||
### 3.1 Services
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **name**: `TEXT` unique
|
||||
* **description**: `TEXT`
|
||||
* **default\_billing\_mode**: `ENUM('per_second','per_request')`
|
||||
* **default\_price**: `NUMERIC(38,18)` (≥0)
|
||||
* **default\_currency**: FK → `currencies(asset_code)`
|
||||
* **max\_request\_seconds**: `INT` (>0 or `NULL`)
|
||||
* **schema\_heroscript**: `TEXT`
|
||||
* **schema\_json**: `JSONB`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
#### Accepted Currencies (per service)
|
||||
|
||||
* **service\_id**: FK → `services(id)`
|
||||
* **asset\_code**: FK → `currencies(asset_code)`
|
||||
* **price\_override**: `NUMERIC(38,18)` (optional)
|
||||
* **billing\_mode\_override**: `ENUM` (optional)
|
||||
Primary key: `(service_id, asset_code)`
|
||||
|
||||
### 3.2 Service Groups
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **name**: `TEXT` unique
|
||||
* **description**: `TEXT`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
#### Group Memberships
|
||||
|
||||
* **group\_id**: FK → `service_groups(id)`
|
||||
* **service\_id**: FK → `services(id)`
|
||||
Primary key: `(group_id, service_id)`
|
||||
|
||||
---
|
||||
|
||||
## 4) Providers & Runners
|
||||
|
||||
### 4.1 Service Providers
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **account\_id**: FK → `accounts(id)` (the owning account)
|
||||
* **name**: `TEXT` unique
|
||||
* **description**: `TEXT`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
#### Providers Offer Groups
|
||||
|
||||
* **provider\_id**: FK → `service_providers(id)`
|
||||
* **group\_id**: FK → `service_groups(id)`
|
||||
Primary key: `(provider_id, group_id)`
|
||||
|
||||
#### Provider Pricing Overrides (optional)
|
||||
|
||||
* **provider\_id**: FK → `service_providers(id)`
|
||||
* **service\_id**: FK → `services(id)`
|
||||
* **asset\_code**: FK → `currencies(asset_code)` (nullable for currency-agnostic override)
|
||||
* **price\_override**: `NUMERIC(38,18)` (optional)
|
||||
* **billing\_mode\_override**: `ENUM` (optional)
|
||||
* **max\_request\_seconds\_override**: `INT` (optional)
|
||||
Primary key: `(provider_id, service_id, asset_code)`
|
||||
|
||||
### 4.2 Runners
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **address**: `INET` (must be IPv6)
|
||||
* **name**: `TEXT`
|
||||
* **description**: `TEXT`
|
||||
* **pubkey**: `BYTEA` (optional)
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
#### Runner Ownership (many-to-many)
|
||||
|
||||
* **runner\_id**: FK → `runners(id)`
|
||||
* **provider\_id**: FK → `service_providers(id)`
|
||||
Primary key: `(runner_id, provider_id)`
|
||||
|
||||
#### Routing (provider → service/service\_group → runners)
|
||||
|
||||
* **provider\_service\_runners**: `(provider_id, service_id, runner_id)` PK
|
||||
* **provider\_service\_group\_runners**: `(provider_id, group_id, runner_id)` PK
|
||||
|
||||
---
|
||||
|
||||
## 5) Subscriptions & Spend Control
|
||||
|
||||
A subscription authorizes an **account** to use either a **service** **or** a **service group**, with optional spend limits and allowed providers.
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **account\_id**: FK → `accounts(id)`
|
||||
* **service\_id** *xor* **group\_id**: FK (exactly one must be set)
|
||||
* **secret**: `BYTEA` (random, provided by subscriber; recommend storing a hash)
|
||||
* **subscription\_data**: `JSONB` (free-form)
|
||||
* **limit\_amount**: `NUMERIC(38,18)` (optional)
|
||||
* **limit\_currency**: FK → `currencies(asset_code)` (optional)
|
||||
* **limit\_period**: `ENUM('hour','day','month')` (optional)
|
||||
* **active**: `BOOLEAN` default `TRUE`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
#### Allowed Providers per Subscription
|
||||
|
||||
* **subscription\_id**: FK → `subscriptions(id)`
|
||||
* **provider\_id**: FK → `service_providers(id)`
|
||||
Primary key: `(subscription_id, provider_id)`
|
||||
|
||||
**Intended Use:**
|
||||
|
||||
* Subscribers bound spending by amount/currency/period.
|
||||
* Merchant (provider) can claim charges for requests fulfilled under an active subscription, within limits, and only if listed in `subscription_providers`.
|
||||
|
||||
---
|
||||
|
||||
## 6) Requests & Billing
|
||||
|
||||
### 6.1 Request Lifecycle
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **account\_id**: FK → `accounts(id)`
|
||||
* **subscription\_id**: FK → `subscriptions(id)`
|
||||
* **provider\_id**: FK → `service_providers(id)`
|
||||
* **service\_id**: FK → `services(id)`
|
||||
* **runner\_id**: FK → `runners(id)` (nullable)
|
||||
* **request\_schema**: `JSONB` (payload matching `schema_json`/`schema_heroscript`)
|
||||
* **started\_at**, **ended\_at**: `TIMESTAMPTZ`
|
||||
* **status**: `ENUM('pending','running','succeeded','failed','canceled')`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
### 6.2 Billing Ledger (append-only)
|
||||
|
||||
* **id**: `BIGINT` identity
|
||||
* **account\_id**: FK → `accounts(id)`
|
||||
* **provider\_id**: FK → `service_providers(id)` (nullable)
|
||||
* **service\_id**: FK → `services(id)` (nullable)
|
||||
* **request\_id**: FK → `requests(id)` (nullable)
|
||||
* **amount**: `NUMERIC(38,18)` (debit = positive, credit/refund = negative)
|
||||
* **asset\_code**: FK → `currencies(asset_code)`
|
||||
* **entry\_type**: `ENUM('debit','credit','adjustment')`
|
||||
* **description**: `TEXT`
|
||||
* **created\_at**: `TIMESTAMPTZ`
|
||||
|
||||
**Balances View (example):**
|
||||
|
||||
* `account_balances(account_id, asset_code, balance)` as a view over `billing_ledger`.
|
||||
|
||||
---
|
||||
|
||||
## 7) Pricing Precedence
|
||||
|
||||
When computing the **effective** pricing, billing mode, and max duration for a `(provider, service, currency)`:
|
||||
|
||||
1. **Provider override for (service, asset\_code)** — if present, use it.
|
||||
2. **Service accepted currency override** — if present, use it.
|
||||
3. **Service defaults** — fallback.
|
||||
|
||||
If `billing_mode` or `max_request_seconds` are not overridden at steps (1) or (2), inherit from the next step down.
|
||||
|
||||
---
|
||||
|
||||
## 8) Key Constraints & Validations
|
||||
|
||||
* All identity ids are non-negative (`CHECK (id >= 0)`).
|
||||
* Runner IPv6 enforcement: `CHECK (family(address) = 6)`.
|
||||
* Subscriptions must point to **exactly one** of `service_id` or `group_id`.
|
||||
* Prices and limits must be non-negative if set.
|
||||
* Unique natural keys where appropriate: service names, provider names, currency asset codes, account pubkeys.
|
||||
|
||||
---
|
||||
|
||||
## 9) Mermaid Diagrams
|
||||
|
||||
### 9.1 Entity–Relationship Overview
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
ACCOUNTS ||--o{ SERVICE_PROVIDERS : "owns via account_id"
|
||||
ACCOUNTS ||--o{ SUBSCRIPTIONS : has
|
||||
CURRENCIES ||--o{ SERVICES : "default_currency"
|
||||
CURRENCIES ||--o{ SERVICE_ACCEPTED_CURRENCIES : "asset_code"
|
||||
CURRENCIES ||--o{ PROVIDER_SERVICE_OVERRIDES : "asset_code"
|
||||
CURRENCIES ||--o{ BILLING_LEDGER : "asset_code"
|
||||
|
||||
SERVICES ||--o{ SERVICE_ACCEPTED_CURRENCIES : has
|
||||
SERVICES ||--o{ SERVICE_GROUP_MEMBERS : member_of
|
||||
SERVICE_GROUPS ||--o{ SERVICE_GROUP_MEMBERS : contains
|
||||
|
||||
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_GROUPS : offers
|
||||
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_OVERRIDES : sets
|
||||
SERVICE_PROVIDERS ||--o{ RUNNER_OWNERS : owns
|
||||
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_RUNNERS : routes
|
||||
SERVICE_PROVIDERS ||--o{ PROVIDER_SERVICE_GROUP_RUNNERS : routes
|
||||
|
||||
RUNNERS ||--o{ RUNNER_OWNERS : owned_by
|
||||
RUNNERS ||--o{ PROVIDER_SERVICE_RUNNERS : executes
|
||||
RUNNERS ||--o{ PROVIDER_SERVICE_GROUP_RUNNERS : executes
|
||||
|
||||
SUBSCRIPTIONS ||--o{ SUBSCRIPTION_PROVIDERS : allow
|
||||
SERVICE_PROVIDERS ||--o{ SUBSCRIPTION_PROVIDERS : allowed
|
||||
|
||||
REQUESTS }o--|| ACCOUNTS : by
|
||||
REQUESTS }o--|| SUBSCRIPTIONS : under
|
||||
REQUESTS }o--|| SERVICE_PROVIDERS : via
|
||||
REQUESTS }o--|| SERVICES : for
|
||||
REQUESTS }o--o{ RUNNERS : executed_by
|
||||
|
||||
BILLING_LEDGER }o--|| ACCOUNTS : charges
|
||||
BILLING_LEDGER }o--o{ SERVICES : reference
|
||||
BILLING_LEDGER }o--o{ SERVICE_PROVIDERS : reference
|
||||
BILLING_LEDGER }o--o{ REQUESTS : reference
|
||||
```
|
||||
|
||||
### 9.2 Request Flow (Happy Path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant AC as Account
|
||||
participant API as Broker/API
|
||||
participant PR as Provider
|
||||
participant RU as Runner
|
||||
participant DB as PostgreSQL
|
||||
|
||||
AC->>API: Submit request (subscription_id, service_id, payload, secret)
|
||||
API->>DB: Validate subscription (active, provider allowed, spend limits)
|
||||
DB-->>API: OK + effective pricing (resolve precedence)
|
||||
API->>PR: Dispatch request (service, payload)
|
||||
PR->>DB: Select runner (provider_service_runners / group runners)
|
||||
PR->>RU: Start job (payload)
|
||||
RU-->>PR: Job started (started_at)
|
||||
PR->>DB: Update REQUESTS (status=running, started_at)
|
||||
RU-->>PR: Job finished (duration, result)
|
||||
PR->>DB: Update REQUESTS (status=succeeded, ended_at)
|
||||
API->>DB: Insert BILLING_LEDGER (debit per effective price)
|
||||
DB-->>API: Ledger entry id
|
||||
API-->>AC: Return result + charge info
|
||||
```
|
||||
|
||||
### 9.3 Pricing Resolution
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Input: provider_id, service_id, asset_code] --> B{Provider override exists for (service, asset_code)?}
|
||||
B -- Yes --> P1[Use provider price/mode/max]
|
||||
B -- No --> C{Service accepted currency override exists?}
|
||||
C -- Yes --> P2[Use service currency price/mode]
|
||||
C -- No --> P3[Use service defaults]
|
||||
P1 --> OUT[Effective pricing]
|
||||
P2 --> OUT
|
||||
P3 --> OUT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10) Operational Notes
|
||||
|
||||
* **Secrets:** store a hash (e.g., `digest(secret,'sha256')`) rather than raw `secret`. Keep the original only client-side.
|
||||
* **Limits enforcement:** before insert of a debit ledger entry, compute period window (hour/day/month UTC or tenant TZ) and enforce `SUM(amount) + new_amount ≤ limit_amount`.
|
||||
* **Durations:** enforce `max_request_seconds` (effective) at orchestration and/or via DB trigger on `REQUESTS` when transitioning to `running/succeeded`.
|
||||
* **Routing:** prefer `provider_service_runners` when a request targets a service directly; otherwise use the union of runners from `provider_service_group_runners` for the group.
|
||||
* **Balances:** serve balance queries via the `account_balances` view or a materialized cache updated by triggers/jobs.
|
||||
|
||||
---
|
||||
|
||||
## 11) Example Effective Pricing Query (sketch)
|
||||
|
||||
```sql
|
||||
-- Inputs: :provider_id, :service_id, :asset_code
|
||||
WITH p AS (
|
||||
SELECT price_override, billing_mode_override, max_request_seconds_override
|
||||
FROM provider_service_overrides
|
||||
WHERE provider_id = :provider_id
|
||||
AND service_id = :service_id
|
||||
AND (asset_code = :asset_code)
|
||||
),
|
||||
sac AS (
|
||||
SELECT price_override, billing_mode_override
|
||||
FROM service_accepted_currencies
|
||||
WHERE service_id = :service_id AND asset_code = :asset_code
|
||||
),
|
||||
svc AS (
|
||||
SELECT default_price AS price, default_billing_mode AS mode, max_request_seconds
|
||||
FROM services WHERE id = :service_id
|
||||
)
|
||||
SELECT
|
||||
COALESCE(p.price_override, sac.price_override, svc.price) AS effective_price,
|
||||
COALESCE(p.billing_mode_override, sac.billing_mode_override, svc.mode) AS effective_mode,
|
||||
COALESCE(p.max_request_seconds_override, svc.max_request_seconds) AS effective_max_seconds;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12) Indices (non-exhaustive)
|
||||
|
||||
* `services(default_currency)`
|
||||
* `service_accepted_currencies(service_id)`
|
||||
* `provider_service_overrides(service_id, provider_id)`
|
||||
* `requests(account_id)`, `requests(provider_id)`, `requests(service_id)`
|
||||
* `billing_ledger(account_id, asset_code)`
|
||||
* `subscriptions(account_id) WHERE active`
|
||||
|
||||
---
|
||||
|
||||
## 13) Migration & Compatibility
|
||||
|
||||
* Prefer additive migrations (new columns/tables) to avoid downtime.
|
||||
* Use `ENUM` via `CREATE TYPE`; when extending, plan for `ALTER TYPE ... ADD VALUE`.
|
||||
* For high-write ledgers, consider partitioning `billing_ledger` by `created_at` (monthly) and indexing partitions.
|
||||
|
||||
---
|
||||
|
||||
## 14) Non-Goals
|
||||
|
||||
* Wallet custody and on-chain settlement are out of scope.
|
||||
* SLA tracking and detailed observability (metrics/log schema) are not part of this spec.
|
||||
|
||||
---
|
||||
|
||||
## 15) Acceptance Criteria
|
||||
|
||||
* Can represent services, groups, and providers with currency-specific pricing.
|
||||
* Can route requests to runners by service or group.
|
||||
* Can authorize usage via subscriptions, enforce spend limits, and record charges.
|
||||
* Can reconstruct balances and audit via append-only ledger.
|
||||
|
||||
---
|
||||
|
||||
**End of Spec**
|
225
specs/billingmanager_research/conceptnote.md
Normal file
225
specs/billingmanager_research/conceptnote.md
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
# Concept Note: Generic Billing & Tracking Framework
|
||||
|
||||
## 1) Purpose
|
||||
|
||||
The model is designed to support a **flexible, generic, and auditable** billing environment that can be applied across diverse services and providers — from compute time billing to per-request API usage, across multiple currencies, with dynamic provider-specific overrides.
|
||||
|
||||
It is **not tied to a single business domain** — the same framework can be used for:
|
||||
|
||||
* Cloud compute time (per second)
|
||||
* API transactions (per request)
|
||||
* Data transfer charges
|
||||
* Managed service subscriptions
|
||||
* Brokered third-party service reselling
|
||||
|
||||
---
|
||||
|
||||
## 2) Key Concepts
|
||||
|
||||
### 2.1 Accounts
|
||||
|
||||
An **account** represents an economic actor in the system — typically a customer or a service provider.
|
||||
|
||||
* Identified by a **public key** (for authentication & cryptographic signing).
|
||||
* Every billing action traces back to an account.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Currencies & Asset Codes
|
||||
|
||||
The system supports **multiple currencies** (crypto or fiat) via **asset codes**.
|
||||
|
||||
* Asset codes identify the unit of billing (e.g. `USDC-ETH`, `EUR`, `LND`).
|
||||
* Currencies are **decoupled from services** so you can add or remove supported assets at any time.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Services & Groups
|
||||
|
||||
* **Service** = a billable offering (e.g., "Speech-to-Text", "VM Hosting").
|
||||
|
||||
* Has a **billing mode** (`per_second` or `per_request`).
|
||||
* Has a **default price** and **default currency**.
|
||||
* Supports **multiple accepted currencies** with optional per-currency pricing overrides.
|
||||
* Has execution constraints (e.g. `max_request_seconds`).
|
||||
* Includes structured schemas for request payloads.
|
||||
|
||||
* **Service Group** = a logical grouping of services.
|
||||
|
||||
* Groups make it easy to **bundle related services** and manage them together.
|
||||
* Providers can offer entire groups rather than individual services.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Service Providers
|
||||
|
||||
A **service provider** is an **account** that offers services or service groups.
|
||||
They can:
|
||||
|
||||
* Override **pricing** for their offered services (per currency).
|
||||
* Route requests to their own **runners** (execution agents).
|
||||
* Manage multiple **service groups** under one provider identity.
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Runners
|
||||
|
||||
A **runner** is an execution agent — a node, VM, or service endpoint that can fulfill requests.
|
||||
|
||||
* Identified by an **IPv6 address** (supports Mycelium or other overlay networks).
|
||||
* Can be owned by one or multiple providers.
|
||||
* Providers map **services/groups → runners** to define routing.
|
||||
|
||||
---
|
||||
|
||||
### 2.6 Subscriptions
|
||||
|
||||
A **subscription** is **the authorization mechanism** for usage and spending control:
|
||||
|
||||
* Links an **account** to a **service** or **service group**.
|
||||
* Defines **spending limits** (amount, currency, period: hour/day/month).
|
||||
* Restricts which **providers** are allowed to serve the subscription.
|
||||
* Uses a **secret** chosen by the subscriber — providers use this to claim charges.
|
||||
|
||||
---
|
||||
|
||||
### 2.7 Requests
|
||||
|
||||
A **request** represents a single execution under a subscription:
|
||||
|
||||
* Tied to **account**, **subscription**, **provider**, **service**, and optionally **runner**.
|
||||
* Has **status** (`pending`, `running`, `succeeded`, `failed`, `canceled`).
|
||||
* Records start/end times for duration-based billing.
|
||||
|
||||
---
|
||||
|
||||
### 2.8 Billing Ledger
|
||||
|
||||
The **ledger** is **append-only** — the source of truth for all charges and credits.
|
||||
|
||||
* Each entry records:
|
||||
|
||||
* `amount` (positive = debit, negative = credit/refund)
|
||||
* `asset_code`
|
||||
* Links to `account`, `provider`, `service`, and/or `request`
|
||||
* From the ledger, **balances** can be reconstructed at any time.
|
||||
|
||||
---
|
||||
|
||||
## 3) How Billing Works — Step by Step
|
||||
|
||||
### 3.1 Setup
|
||||
|
||||
1. **Define services** with default pricing & schemas.
|
||||
2. **Define currencies** and accepted currencies for services.
|
||||
3. **Group services** into service groups.
|
||||
4. **Onboard providers** (accounts) and associate them with service groups.
|
||||
5. **Assign runners** to services or groups for execution routing.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Subscription Creation
|
||||
|
||||
1. Customer **creates a subscription**:
|
||||
|
||||
* Chooses service or service group.
|
||||
* Sets **spending limit** (amount, currency, period).
|
||||
* Chooses **secret**.
|
||||
* Selects **allowed providers**.
|
||||
2. Subscription is stored in DB.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Request Execution
|
||||
|
||||
1. Customer sends a request to broker/API with:
|
||||
|
||||
* `subscription_id`
|
||||
* Target `service_id`
|
||||
* Payload + signature using account pubkey.
|
||||
2. Broker:
|
||||
|
||||
* Validates **subscription active**.
|
||||
* Validates **provider allowed**.
|
||||
* Checks **spend limit** hasn’t been exceeded for current period.
|
||||
* Resolves **effective price** via:
|
||||
|
||||
1. Provider override (currency-specific)
|
||||
2. Service accepted currency override
|
||||
3. Service default
|
||||
3. Broker selects **runner** from provider’s routing tables.
|
||||
4. Runner executes request and returns result.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Billing Entry
|
||||
|
||||
1. When the request completes:
|
||||
|
||||
* If `per_second` mode → calculate `duration × rate`.
|
||||
* If `per_request` mode → apply flat rate.
|
||||
2. Broker **inserts ledger entry**:
|
||||
|
||||
* Debit from customer account.
|
||||
* Credit to provider account (can be separate entries or aggregated).
|
||||
3. Ledger is append-only — historical billing cannot be altered.
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Balance & Tracking
|
||||
|
||||
* **Current balances** are a sum of all ledger entries per account+currency.
|
||||
* Spend limits are enforced by **querying the ledger** for the current period before each charge.
|
||||
* Audit trails are guaranteed via immutable ledger entries.
|
||||
|
||||
---
|
||||
|
||||
## 4) Why This is Generic & Reusable
|
||||
|
||||
This design **decouples**:
|
||||
|
||||
* **Service definition** from **provider pricing** → multiple providers can sell the same service at different rates.
|
||||
* **Execution agents** (runners) from **service definitions** → easy scaling or outsourcing of execution.
|
||||
* **Billing rules** (per-second vs per-request) from **subscription limits** → same service can be sold in different billing modes.
|
||||
* **Currencies** from the service → enabling multi-asset billing without changing the service definition.
|
||||
|
||||
Because of these separations, you can:
|
||||
|
||||
* Reuse the model for **compute**, **APIs**, **storage**, **SaaS features**, etc.
|
||||
* Plug in different **payment backends** (on-chain, centralized payment processor, prepaid balance).
|
||||
* Use the same model for **internal cost allocation** or **external customer billing**.
|
||||
|
||||
---
|
||||
|
||||
## 5) Potential Extensions
|
||||
|
||||
* **Prepaid model**: enforce that ledger debits can’t exceed balance.
|
||||
* **On-chain settlement**: periodically export ledger entries to blockchain transactions.
|
||||
* **Discount models**: percentage or fixed-amount discounts per subscription.
|
||||
* **Usage analytics**: aggregate requests/billing by time period, provider, or service.
|
||||
* **SLAs**: link billing adjustments to performance metrics in requests.
|
||||
|
||||
---
|
||||
|
||||
## 6) Conceptual Diagram — Billing Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Customer Account
|
||||
participant B as Broker/API
|
||||
participant P as Provider
|
||||
participant R as Runner
|
||||
participant DB as Ledger DB
|
||||
|
||||
C->>B: Request(service, subscription, payload, secret)
|
||||
B->>DB: Validate subscription & spend limit
|
||||
DB-->>B: OK + effective pricing
|
||||
B->>P: Forward request
|
||||
P->>R: Execute request
|
||||
R-->>P: Result + execution time
|
||||
P->>B: Return result
|
||||
B->>DB: Insert debit (customer) + credit (provider)
|
||||
DB-->>B: Ledger updated
|
||||
B-->>C: Return result + charge info
|
||||
```
|
234
specs/billingmanager_research/schema.sql
Normal file
234
specs/billingmanager_research/schema.sql
Normal file
@@ -0,0 +1,234 @@
|
||||
-- Enable useful extensions (optional)
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto; -- for digests/hashes if you want
|
||||
CREATE EXTENSION IF NOT EXISTS btree_gist; -- for exclusion/partial indexes
|
||||
|
||||
-- =========================
|
||||
-- Core: Accounts & Currency
|
||||
-- =========================
|
||||
|
||||
CREATE TABLE accounts (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
pubkey BYTEA NOT NULL UNIQUE,
|
||||
display_name TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (id >= 0)
|
||||
);
|
||||
|
||||
CREATE TABLE currencies (
|
||||
asset_code TEXT PRIMARY KEY, -- e.g. "USDC-ETH", "EUR", "LND"
|
||||
name TEXT NOT NULL,
|
||||
symbol TEXT, -- e.g. "$", "€"
|
||||
decimals INT NOT NULL DEFAULT 2, -- how many decimal places
|
||||
UNIQUE (name)
|
||||
);
|
||||
|
||||
-- =========================
|
||||
-- Services & Groups
|
||||
-- =========================
|
||||
|
||||
CREATE TYPE billing_mode AS ENUM ('per_second', 'per_request');
|
||||
|
||||
CREATE TABLE services (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
default_billing_mode billing_mode NOT NULL,
|
||||
default_price NUMERIC(38, 18) NOT NULL, -- default price in "unit currency" (see accepted currencies)
|
||||
default_currency TEXT NOT NULL REFERENCES currencies(asset_code) ON UPDATE CASCADE,
|
||||
max_request_seconds INTEGER, -- nullable means no cap
|
||||
schema_heroscript TEXT,
|
||||
schema_json JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (id >= 0),
|
||||
CHECK (default_price >= 0),
|
||||
CHECK (max_request_seconds IS NULL OR max_request_seconds > 0)
|
||||
);
|
||||
|
||||
-- Accepted currencies for a service (subset + optional specific price per currency)
|
||||
CREATE TABLE service_accepted_currencies (
|
||||
service_id BIGINT NOT NULL REFERENCES services(id) ON DELETE CASCADE,
|
||||
asset_code TEXT NOT NULL REFERENCES currencies(asset_code) ON UPDATE CASCADE,
|
||||
price_override NUMERIC(38, 18), -- if set, overrides default_price for this currency
|
||||
billing_mode_override billing_mode, -- if set, overrides default_billing_mode
|
||||
PRIMARY KEY (service_id, asset_code),
|
||||
CHECK (price_override IS NULL OR price_override >= 0)
|
||||
);
|
||||
|
||||
CREATE TABLE service_groups (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (id >= 0)
|
||||
);
|
||||
|
||||
CREATE TABLE service_group_members (
|
||||
group_id BIGINT NOT NULL REFERENCES service_groups(id) ON DELETE CASCADE,
|
||||
service_id BIGINT NOT NULL REFERENCES services(id) ON DELETE RESTRICT,
|
||||
PRIMARY KEY (group_id, service_id)
|
||||
);
|
||||
|
||||
-- =========================
|
||||
-- Providers, Runners, Routing
|
||||
-- =========================
|
||||
|
||||
CREATE TABLE service_providers (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, -- provider is an account
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (name),
|
||||
CHECK (id >= 0)
|
||||
);
|
||||
|
||||
-- Providers can offer groups (which imply their services)
|
||||
CREATE TABLE provider_service_groups (
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES service_groups(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (provider_id, group_id)
|
||||
);
|
||||
|
||||
-- Providers may set per-service overrides (price/mode/max seconds) (optionally per currency)
|
||||
CREATE TABLE provider_service_overrides (
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
service_id BIGINT NOT NULL REFERENCES services(id) ON DELETE CASCADE,
|
||||
asset_code TEXT REFERENCES currencies(asset_code) ON UPDATE CASCADE,
|
||||
price_override NUMERIC(38, 18),
|
||||
billing_mode_override billing_mode,
|
||||
max_request_seconds_override INTEGER,
|
||||
PRIMARY KEY (provider_id, service_id, asset_code),
|
||||
CHECK (price_override IS NULL OR price_override >= 0),
|
||||
CHECK (max_request_seconds_override IS NULL OR max_request_seconds_override > 0)
|
||||
);
|
||||
|
||||
-- Runners
|
||||
CREATE TABLE runners (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
address INET NOT NULL, -- IPv6 (INET supports both IPv4/IPv6; require v6 via CHECK below if you like)
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
pubkey BYTEA, -- optional
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (address),
|
||||
CHECK (id >= 0),
|
||||
CHECK (family(address) = 6) -- ensure IPv6
|
||||
);
|
||||
|
||||
-- Runner ownership: a runner can be owned by multiple providers
|
||||
CREATE TABLE runner_owners (
|
||||
runner_id BIGINT NOT NULL REFERENCES runners(id) ON DELETE CASCADE,
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (runner_id, provider_id)
|
||||
);
|
||||
|
||||
-- Routing: link providers' services to specific runners
|
||||
CREATE TABLE provider_service_runners (
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
service_id BIGINT NOT NULL REFERENCES services(id) ON DELETE CASCADE,
|
||||
runner_id BIGINT NOT NULL REFERENCES runners(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (provider_id, service_id, runner_id)
|
||||
);
|
||||
|
||||
-- Routing: link providers' service groups to runners
|
||||
CREATE TABLE provider_service_group_runners (
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES service_groups(id) ON DELETE CASCADE,
|
||||
runner_id BIGINT NOT NULL REFERENCES runners(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (provider_id, group_id, runner_id)
|
||||
);
|
||||
|
||||
-- =========================
|
||||
-- Subscriptions & Spend Control
|
||||
-- =========================
|
||||
|
||||
CREATE TYPE spend_period AS ENUM ('hour', 'day', 'month');
|
||||
|
||||
-- A subscription ties an account to a specific service OR a service group, with spend limits and allowed providers
|
||||
CREATE TABLE subscriptions (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
||||
service_id BIGINT REFERENCES services(id) ON DELETE CASCADE,
|
||||
group_id BIGINT REFERENCES service_groups(id) ON DELETE CASCADE,
|
||||
secret BYTEA NOT NULL, -- caller-chosen secret (consider storing a hash instead)
|
||||
subscription_data JSONB, -- arbitrary client-supplied info
|
||||
limit_amount NUMERIC(38, 18), -- allowed spend in the selected currency per period
|
||||
limit_currency TEXT REFERENCES currencies(asset_code) ON UPDATE CASCADE,
|
||||
limit_period spend_period, -- period for the limit
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
-- Ensure exactly one of service_id or group_id
|
||||
CHECK ( (service_id IS NOT NULL) <> (group_id IS NOT NULL) ),
|
||||
CHECK (limit_amount IS NULL OR limit_amount >= 0),
|
||||
CHECK (id >= 0)
|
||||
);
|
||||
|
||||
-- Providers that are allowed to serve under a subscription
|
||||
CREATE TABLE subscription_providers (
|
||||
subscription_id BIGINT NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (subscription_id, provider_id)
|
||||
);
|
||||
|
||||
-- =========================
|
||||
-- Usage, Requests & Billing
|
||||
-- =========================
|
||||
|
||||
-- A request lifecycle record (optional but useful for auditing and max duration enforcement)
|
||||
CREATE TYPE request_status AS ENUM ('pending', 'running', 'succeeded', 'failed', 'canceled');
|
||||
|
||||
CREATE TABLE requests (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
||||
subscription_id BIGINT NOT NULL REFERENCES subscriptions(id) ON DELETE RESTRICT,
|
||||
provider_id BIGINT NOT NULL REFERENCES service_providers(id) ON DELETE RESTRICT,
|
||||
service_id BIGINT NOT NULL REFERENCES services(id) ON DELETE RESTRICT,
|
||||
runner_id BIGINT REFERENCES runners(id) ON DELETE SET NULL,
|
||||
request_schema JSONB, -- concrete task payload (conforms to schema_json/heroscript)
|
||||
started_at TIMESTAMPTZ,
|
||||
ended_at TIMESTAMPTZ,
|
||||
status request_status NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (id >= 0),
|
||||
CHECK (ended_at IS NULL OR started_at IS NULL OR ended_at >= started_at)
|
||||
);
|
||||
|
||||
-- Billing ledger (debits/credits). Positive amount = debit to account (charge). Negative = credit/refund.
|
||||
CREATE TYPE ledger_entry_type AS ENUM ('debit', 'credit', 'adjustment');
|
||||
|
||||
CREATE TABLE billing_ledger (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
||||
provider_id BIGINT REFERENCES service_providers(id) ON DELETE SET NULL,
|
||||
service_id BIGINT REFERENCES services(id) ON DELETE SET NULL,
|
||||
request_id BIGINT REFERENCES requests(id) ON DELETE SET NULL,
|
||||
amount NUMERIC(38, 18) NOT NULL, -- positive for debit, negative for credit
|
||||
asset_code TEXT NOT NULL REFERENCES currencies(asset_code) ON UPDATE CASCADE,
|
||||
entry_type ledger_entry_type NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (id >= 0)
|
||||
);
|
||||
|
||||
-- Optional: running balances per account/currency (materialized view or real-time view)
|
||||
-- This is a plain view; for performance, you might maintain a cached table.
|
||||
CREATE VIEW account_balances AS
|
||||
SELECT
|
||||
account_id,
|
||||
asset_code,
|
||||
SUM(amount) AS balance
|
||||
FROM billing_ledger
|
||||
GROUP BY account_id, asset_code;
|
||||
|
||||
-- =========================
|
||||
-- Helpful Indexes
|
||||
-- =========================
|
||||
|
||||
CREATE INDEX idx_services_default_currency ON services(default_currency);
|
||||
CREATE INDEX idx_service_accepted_currencies_service ON service_accepted_currencies(service_id);
|
||||
CREATE INDEX idx_provider_overrides_service ON provider_service_overrides(service_id);
|
||||
CREATE INDEX idx_requests_account ON requests(account_id);
|
||||
CREATE INDEX idx_requests_provider ON requests(provider_id);
|
||||
CREATE INDEX idx_requests_service ON requests(service_id);
|
||||
CREATE INDEX idx_billing_account_currency ON billing_ledger(account_id, asset_code);
|
||||
CREATE INDEX idx_subscriptions_account_active ON subscriptions(account_id) WHERE active;
|
266
specs/billingmanager_research/summary.md
Normal file
266
specs/billingmanager_research/summary.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# Billing Logic — Whiteboard Version (for Devs)
|
||||
|
||||
## 1) Inputs You Always Need
|
||||
|
||||
* `account_id`, `subscription_id`
|
||||
* `service_id` (or group → resolved to a service at dispatch)
|
||||
* `provider_id`, `asset_code`
|
||||
* `payload` (validated against service schema)
|
||||
* (Optional) `runner_id`
|
||||
* Idempotency key for the request (client-provided)
|
||||
|
||||
---
|
||||
|
||||
## 2) Gatekeeping (Hard Checks)
|
||||
|
||||
1. **Subscription**
|
||||
|
||||
* Must be `active`.
|
||||
* Must target **exactly one** of {service, group}.
|
||||
* If group: ensure `service_id` is a member.
|
||||
|
||||
2. **Provider Allowlist**
|
||||
|
||||
* If `subscription_providers` exists → `provider_id` must be listed.
|
||||
|
||||
3. **Spend Limit** (if set)
|
||||
|
||||
* Compute window by `limit_period` (`hour`/`day`/`month`, UTC unless tenant TZ).
|
||||
* Current period spend = `SUM(ledger.amount WHERE account & currency & period)`.
|
||||
* `current_spend + estimated_charge ≤ limit_amount`.
|
||||
|
||||
4. **Max Duration** (effective; see §3):
|
||||
|
||||
* If billing mode is `per_second`, reject if requested/max exceeds effective cap.
|
||||
|
||||
---
|
||||
|
||||
## 3) Effective Pricing (Single Resolution Function)
|
||||
|
||||
Inputs: `provider_id`, `service_id`, `asset_code`
|
||||
|
||||
Precedence:
|
||||
|
||||
1. `provider_service_overrides` for `(service_id, asset_code)`
|
||||
2. `service_accepted_currencies` for `(service_id, asset_code)`
|
||||
3. `services` defaults
|
||||
|
||||
Outputs:
|
||||
|
||||
* `effective_billing_mode ∈ {per_request, per_second}`
|
||||
* `effective_price` (NUMERIC)
|
||||
* `effective_max_request_seconds` (nullable)
|
||||
|
||||
---
|
||||
|
||||
## 4) Request Lifecycle (States)
|
||||
|
||||
* `pending` → `running` → (`succeeded` | `failed` | `canceled`)
|
||||
* Timestamps: set `started_at` on `running`, `ended_at` on terminal states.
|
||||
* Enforce `ended_at ≥ started_at` and `duration ≤ effective_max_request_seconds` (if set).
|
||||
|
||||
---
|
||||
|
||||
## 5) Charging Rules
|
||||
|
||||
### A) Per Request
|
||||
|
||||
```
|
||||
charge = effective_price
|
||||
```
|
||||
|
||||
### B) Per Second
|
||||
|
||||
```
|
||||
duration_seconds = ceil(extract(epoch from (ended_at - started_at)))
|
||||
charge = duration_seconds * effective_price
|
||||
```
|
||||
|
||||
* Cap with `effective_max_request_seconds` if present.
|
||||
* If ended early/failed before `started_at`: charge = 0.
|
||||
|
||||
---
|
||||
|
||||
## 6) Idempotency & Atomicity
|
||||
|
||||
* **Idempotency key** per `(account_id, subscription_id, provider_id, service_id, request_external_id)`; store on `requests` and enforce unique index.
|
||||
* **Single transaction** to:
|
||||
|
||||
1. finalize `REQUESTS` status + timestamps,
|
||||
2. insert **one** debit entry into `billing_ledger`.
|
||||
* Never mutate ledger entries; use compensating **credit** entries for adjustments/refunds.
|
||||
|
||||
---
|
||||
|
||||
## 7) Spend-Limit Enforcement (Before Charging)
|
||||
|
||||
Pseudocode (SQL-ish):
|
||||
|
||||
```sql
|
||||
WITH window AS (
|
||||
SELECT tsrange(period_start(:limit_period), period_end(:limit_period)) AS w
|
||||
),
|
||||
spent AS (
|
||||
SELECT COALESCE(SUM(amount), 0) AS total
|
||||
FROM billing_ledger, window
|
||||
WHERE account_id = :account_id
|
||||
AND asset_code = :asset_code
|
||||
AND created_at <@ (SELECT w FROM window)
|
||||
),
|
||||
check AS (
|
||||
SELECT (spent.total + :estimated_charge) <= :limit_amount AS ok FROM spent
|
||||
)
|
||||
SELECT ok FROM check;
|
||||
```
|
||||
|
||||
* If not ok → reject before dispatch, or allow but **set hard cap** on max seconds and auto-stop at limit.
|
||||
|
||||
---
|
||||
|
||||
## 8) Suggested DB Operations (Happy Path)
|
||||
|
||||
1. **Create request**
|
||||
|
||||
```sql
|
||||
INSERT INTO requests (...)
|
||||
VALUES (...)
|
||||
ON CONFLICT (idempotency_key) DO NOTHING
|
||||
RETURNING id;
|
||||
```
|
||||
|
||||
2. **Start execution**
|
||||
|
||||
```sql
|
||||
UPDATE requests
|
||||
SET status='running', started_at=now()
|
||||
WHERE id=:id AND status='pending';
|
||||
```
|
||||
|
||||
3. **Finish & bill** (single transaction)
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
|
||||
-- lock for update to avoid double-billing
|
||||
UPDATE requests
|
||||
SET status=:final_status, ended_at=now()
|
||||
WHERE id=:id AND status='running'
|
||||
RETURNING started_at, ended_at;
|
||||
|
||||
-- compute charge in app (see §5), re-check spend window here
|
||||
|
||||
INSERT INTO billing_ledger (
|
||||
account_id, provider_id, service_id, request_id,
|
||||
amount, asset_code, entry_type, description
|
||||
) VALUES (
|
||||
:account_id, :provider_id, :service_id, :id,
|
||||
:charge, :asset_code, 'debit', :desc
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9) Balances & Reporting
|
||||
|
||||
* **Current balance** = `SUM(billing_ledger.amount) GROUP BY account_id, asset_code`.
|
||||
* Keep a **view** or **materialized view**; refresh asynchronously if needed.
|
||||
* Never rely on cached balance for hard checks — re-check within the billing transaction if **prepaid** semantics are required.
|
||||
|
||||
---
|
||||
|
||||
## 10) Error & Edge Rules
|
||||
|
||||
* If runner fails before `running` → no charge.
|
||||
* If runner starts, then fails:
|
||||
|
||||
* **per\_second**: bill actual seconds (can be 0).
|
||||
* **per\_request**: default is **no charge** unless policy says otherwise; if charging partials, document it.
|
||||
* Partial refunds/adjustments → insert **negative** ledger entries (type `credit`/`adjustment`) tied to the original `request_id`.
|
||||
|
||||
---
|
||||
|
||||
## 11) Minimal Pricing Resolver (Sketch)
|
||||
|
||||
```sql
|
||||
WITH p AS (
|
||||
SELECT price_override AS price,
|
||||
billing_mode_override AS mode,
|
||||
max_request_seconds_override AS maxsec
|
||||
FROM provider_service_overrides
|
||||
WHERE provider_id = :pid AND service_id = :sid AND asset_code = :asset
|
||||
LIMIT 1
|
||||
),
|
||||
sac AS (
|
||||
SELECT price_override AS price,
|
||||
billing_mode_override AS mode
|
||||
FROM service_accepted_currencies
|
||||
WHERE service_id = :sid AND asset_code = :asset
|
||||
LIMIT 1
|
||||
),
|
||||
svc AS (
|
||||
SELECT default_price AS price,
|
||||
default_billing_mode AS mode,
|
||||
max_request_seconds AS maxsec
|
||||
FROM services WHERE id = :sid
|
||||
)
|
||||
SELECT
|
||||
COALESCE(p.price, sac.price, svc.price) AS price,
|
||||
COALESCE(p.mode, sac.mode, svc.mode) AS mode,
|
||||
COALESCE(p.maxsec, svc.maxsec) AS max_seconds;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12) Mermaid — Decision Trees
|
||||
|
||||
### Pricing & Duration
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[provider_id, service_id, asset_code] --> B{Provider override exists?}
|
||||
B -- yes --> P[Use provider price/mode/max]
|
||||
B -- no --> C{Service currency override?}
|
||||
C -- yes --> S[Use service currency price/mode]
|
||||
C -- no --> D[Use service defaults]
|
||||
P --> OUT[effective price/mode/max]
|
||||
S --> OUT
|
||||
D --> OUT
|
||||
```
|
||||
|
||||
### Spend Check & Charge
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S[Has subscription limit?] -->|No| D1[Dispatch]
|
||||
S -->|Yes| C{current_spend + est_charge <= limit?}
|
||||
C -->|No| REJ[Reject or cap duration]
|
||||
C -->|Yes| D1[Dispatch]
|
||||
D1 --> RUN[Run request]
|
||||
RUN --> DONE[Finalize + insert ledger]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13) Security Posture
|
||||
|
||||
* Store **hash of subscription secret**; compare hash on use.
|
||||
* Sign client requests with **account pubkey**; verify before dispatch.
|
||||
* Limit **request schema** to validated fields; reject unknowns.
|
||||
* Enforce **IPv6** for runners where required.
|
||||
|
||||
---
|
||||
|
||||
## 14) What To Implement First
|
||||
|
||||
1. Pricing resolver (single function).
|
||||
2. Spend-window checker (single query).
|
||||
3. Request lifecycle + idempotency.
|
||||
4. Ledger write (append-only) + balances view.
|
||||
|
||||
Everything else layers on top.
|
||||
|
||||
---
|
||||
|
||||
If you want, I can turn this into a small **README.md** with code blocks you can paste into the repo (plus a couple of SQL functions and example tests).
|
@@ -24,16 +24,6 @@ pub enum CurrencyType {
|
||||
custom
|
||||
}
|
||||
|
||||
pub struct Price {
|
||||
pub mut:
|
||||
base_amount f64 // Using f64 for Decimal
|
||||
base_currency string
|
||||
display_currency string
|
||||
display_amount f64 // Using f64 for Decimal
|
||||
formatted_display string
|
||||
conversion_rate f64 // Using f64 for Decimal
|
||||
conversion_timestamp u64 // Unix timestamp
|
||||
}
|
||||
|
||||
pub struct MarketplaceCurrencyConfig {
|
||||
pub mut:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user