diff --git a/.vscode/settings.json b/.vscode/settings.json index f78dcd9..4b8209b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,12 @@ "command": ["treefmt-nix", "--stdin", "neverexists.nix"] } } - } + }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + }, + "files.readonlyInclude": { + "**/.cargo/registry/src/**/*.rs": true, + "**/lib/rustlib/src/rust/library/**/*.rs": true, + }, } diff --git a/Cargo.lock b/Cargo.lock index 7b76bc2..f72b777 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,202 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.22.1", + "bitflags 2.6.0", + "brotli", + "bytes", + "bytestring", + "derive_more 0.99.18", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.90", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-test" +version = "0.1.0" +dependencies = [ + "actix-web", + "futures", + "mongodb", + "serde", + "tokio", + "utoipa", + "utoipa-swagger-ui", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more 0.99.18", + "encoding_rs", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -39,6 +235,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -109,6 +320,15 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "array-init" version = "2.1.0" @@ -280,6 +500,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bson" version = "2.13.0" @@ -340,12 +581,23 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -428,6 +680,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -472,6 +735,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -581,6 +850,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -683,6 +963,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -899,6 +1188,25 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1043,6 +1351,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.2.0" @@ -1060,6 +1379,12 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" @@ -1234,6 +1559,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indexmap" version = "1.9.3" @@ -1286,6 +1617,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.76" @@ -1296,6 +1636,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "libc" version = "0.2.169" @@ -1320,6 +1666,23 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.12" @@ -1330,6 +1693,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -1367,6 +1736,22 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -1383,6 +1768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -1548,6 +1934,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -1614,6 +2006,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "portable-atomic" version = "1.10.0" @@ -1779,6 +2177,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -1810,6 +2214,40 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.90", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1956,6 +2394,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.27" @@ -2098,6 +2545,18 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.11.0" @@ -2139,6 +2598,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2187,6 +2657,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -2524,7 +3000,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "http", + "http 1.2.0", "httparse", "rand", "ring", @@ -2550,6 +3026,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2603,6 +3080,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -2707,6 +3190,48 @@ dependencies = [ "url", ] +[[package]] +name = "utoipa" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +dependencies = [ + "indexmap 2.7.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.90", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4b5ac679cc6dfc5ea3f2823b0291c777750ffd5e13b21137e0f7ac0e8f9617" +dependencies = [ + "actix-web", + "base64 0.22.1", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "1.11.0" @@ -2732,6 +3257,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2820,6 +3355,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3132,3 +3676,62 @@ dependencies = [ "quote", "syn 2.0.90", ] + +[[package]] +name = "zip" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.7.0", + "memchr", + "thiserror 2.0.8", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 780343a..26c810c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "rust/clients/host_agent", "rust/services/workload", "rust/util_libs", + "rust/orchestrator", ] [workspace.dependencies] diff --git a/rust/orchestrator/Cargo.toml b/rust/orchestrator/Cargo.toml new file mode 100755 index 0000000..8af64d4 --- /dev/null +++ b/rust/orchestrator/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "actix-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix-web = "4" +utoipa = { version = "5", features = ["actix_extras"] } +utoipa-swagger-ui = { version = "8.0.3", features = ["actix-web"] } +serde = "1.0.188" +futures = "0.3.28" +tokio = {version = "1.32.0", features = ["full"]} + +[dependencies.mongodb] +version = "3.1.0" \ No newline at end of file diff --git a/rust/orchestrator/src/hello.rs b/rust/orchestrator/src/hello.rs new file mode 100755 index 0000000..4427e77 --- /dev/null +++ b/rust/orchestrator/src/hello.rs @@ -0,0 +1,17 @@ +use actix_web::{get, HttpResponse, Responder}; + +#[utoipa::path( + get, + path = "/test", + responses( + (status = 200, description = "Returns hello world") + ), + description = "test" +)] +#[get("/test")] +async fn hello() -> impl Responder { + HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "Application/text")) + .body("Hello world.") +} diff --git a/rust/orchestrator/src/main.rs b/rust/orchestrator/src/main.rs new file mode 100755 index 0000000..9add73b --- /dev/null +++ b/rust/orchestrator/src/main.rs @@ -0,0 +1,47 @@ +use actix_web::{get, App, HttpResponse, HttpServer, Responder}; +use mongodb::Client; +use std::env; +use utoipa::{openapi::Server, OpenApi}; +use utoipa_swagger_ui::{SwaggerUi, Url}; +mod hello; + +#[derive(OpenApi)] +#[openapi(paths(hello::hello))] +struct ApiDoc; + +#[get("/api-docs/json")] +async fn docs() -> impl Responder { + let mut docs = ApiDoc::openapi(); + docs.servers = Some(vec![Server::new("http://127.0.0.1:3000")]); + HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "Application/json")) + .body(docs.to_pretty_json().unwrap()) +} + +async fn db() -> mongodb::error::Result<()> { + let connection_uri = env::var("DB_CONNECTION_STRING").unwrap(); + let client = Client::with_uri_str(connection_uri).await?; + let _database = client.database("orchestrator"); + + Ok(()) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let _ = db().await; + + println!("Starting Server on port 3000"); + HttpServer::new(|| { + App::new() + .service( + SwaggerUi::new("/api-docs/ui/{_:.*}") + .url(Url::new("api1", "/api-docs/json"), ApiDoc::openapi()), + ) + .service(hello::hello) + .service(docs) + }) + .bind(("0.0.0.0", 3000))? + .run() + .await +} diff --git a/rust/services/workload/src/lib.rs b/rust/services/workload/src/lib.rs index 353d803..eb77366 100644 --- a/rust/services/workload/src/lib.rs +++ b/rust/services/workload/src/lib.rs @@ -18,11 +18,10 @@ use anyhow::{anyhow, Result}; use async_nats::Message; use mongodb::{options::UpdateModifications, Client as MongoDBClient}; use std::{fmt::Debug, sync::Arc}; -use util_libs::{db::{mongodb::{IntoIndexes, MongoCollection, MongoDbAPI}, schemas::{self, Host, Workload, WorkloadState, WorkloadStatus}}, nats_js_client}; -use rand::seq::SliceRandom; +use util_libs::{db::{mongodb::{IntoIndexes, MongoCollection, MongoDbAPI}, schemas::{self, Host, Workload, WorkloadState, WorkloadStatus, MongoDbId}}, nats_js_client}; use std::future::Future; use serde::{Deserialize, Serialize}; -use bson::{self, doc, to_document}; +use bson::{self, doc, to_document, DateTime}; pub const WORKLOAD_SRV_NAME: &str = "WORKLOAD"; pub const WORKLOAD_SRV_SUBJ: &str = "WORKLOAD"; @@ -35,6 +34,7 @@ pub struct WorkloadApi { pub workload_collection: MongoCollection, pub host_collection: MongoCollection, pub user_collection: MongoCollection, + pub developer_collection: MongoCollection, } impl WorkloadApi { @@ -43,6 +43,7 @@ impl WorkloadApi { workload_collection: Self::init_collection(client, schemas::WORKLOAD_COLLECTION_NAME).await?, host_collection: Self::init_collection(client, schemas::HOST_COLLECTION_NAME).await?, user_collection: Self::init_collection(client, schemas::USER_COLLECTION_NAME).await?, + developer_collection: Self::init_collection(client, schemas::DEVELOPER_COLLECTION_NAME).await?, }) } @@ -71,12 +72,12 @@ impl WorkloadApi { let workload_id = self.workload_collection.insert_one_into(workload.clone()).await?; log::info!("Successfully added workload. MongodDB Workload ID={:?}", workload_id); let updated_workload = schemas::Workload { - _id: Some(workload_id), + _id: Some(MongoDbId::parse_str(workload_id)?), ..workload }; Ok(types::ApiResult( WorkloadStatus { - id: updated_workload._id, + id: updated_workload._id.map(|oid| oid.to_hex()), desired: WorkloadState::Reported, actual: WorkloadState::Reported, }, @@ -94,13 +95,20 @@ impl WorkloadApi { msg, WorkloadState::Running, |workload: schemas::Workload| async move { - let workload_query = doc! { "_id": workload._id.clone() }; - let updated_workload = to_document(&workload)?; - self.workload_collection.update_one_within(workload_query, UpdateModifications::Document(updated_workload)).await?; + let workload_query = doc! { "_id": workload._id }; + + // update workload updated_at + let mut workload_doc = workload.clone(); + workload_doc.metadata.updated_at = Some(DateTime::now()); + + // convert workload to document and submit to mongodb + let updated_workload = to_document(&workload_doc)?; + self.workload_collection.update_one_within(workload_query, UpdateModifications::Document(doc!{ "$set": updated_workload })).await?; + log::info!("Successfully updated workload. MongodDB Workload ID={:?}", workload._id); Ok(types::ApiResult( WorkloadStatus { - id: workload._id, + id: workload._id.map(|oid| oid.to_hex()), desired: WorkloadState::Reported, actual: WorkloadState::Reported, }, @@ -119,15 +127,23 @@ impl WorkloadApi { msg, WorkloadState::Removed, |workload_id: schemas::MongoDbId| async move { - let workload_query = doc! { "_id": workload_id.clone() }; - self.workload_collection.delete_one_from(workload_query).await?; + let workload_query = doc! { "_id": workload_id }; + self.workload_collection.update_one_within( + workload_query, + UpdateModifications::Document(doc! { + "$set": { + "metadata.is_deleted": true, + "metadata.deleted_at": DateTime::now() + } + }) + ).await?; log::info!( "Successfully removed workload from the Workload Collection. MongodDB Workload ID={:?}", workload_id ); Ok(types::ApiResult( WorkloadStatus { - id: Some(workload_id), + id: Some(workload_id.to_hex()), desired: WorkloadState::Removed, actual: WorkloadState::Removed, }, @@ -139,7 +155,51 @@ impl WorkloadApi { .await) } + // verifies if a host meets the workload criteria + pub fn verify_host_meets_workload_criteria(&self, workload: Workload, assigned_host: Host) -> bool { + if assigned_host.remaining_capacity.disk < workload.system_specs.capacity.disk { + return false; + } + if assigned_host.remaining_capacity.memory < workload.system_specs.capacity.memory { + return false; + } + if assigned_host.remaining_capacity.cores < workload.system_specs.capacity.cores { + return false; + } + + return true; + } + + // looks through existing hosts to find possible hosts for a given workload + // returns the minimum number of hosts required for workload + pub async fn find_hosts_meeting_workload_criteria(&self, workload: Workload) -> Result, anyhow::Error> { + let pipeline = vec! [ + doc! { + "$match": { + // verify there are enough system resources + "remaining_capacity.disk": { "$gte": workload.system_specs.capacity.disk }, + "remaining_capacity.memory": { "$gte": workload.system_specs.capacity.memory }, + "remaining_capacity.cores": { "$gte": workload.system_specs.capacity.cores }, + + // limit how many workloads a single host can have + "assigned_workloads": { "$lt": 1 } + } + }, + doc! { + // the maximum number of hosts returned should be the minimum hosts required by workload + // sample randomized results and always return back atleast 1 result + "$sample": std::cmp::min(workload.min_hosts as i32, 1) + } + ]; + let results = self.host_collection.aggregate(pipeline).await?; + if results.len() == 0 { + return Err(anyhow!("Could not find a compatible host for this workload={:#?}", workload._id)); + } + Ok(results) + } + // NB: Automatically published by the nats-db-connector + // trigger on mongodb [workload] collection (insert) pub async fn handle_db_insertion(&self, msg: Arc) -> Result { log::debug!("Incoming message for 'WORKLOAD.insert'"); Ok(self.process_request( @@ -160,32 +220,22 @@ impl WorkloadApi { if !workload.assigned_hosts.is_empty() { log::warn!("Attempted to assign host for new workload, but host already exists."); return Ok(types::ApiResult( - WorkloadStatus { - id: Some(workload_id), - desired: WorkloadState::Assigned, - actual: WorkloadState::Assigned, - }, - Some(workload.assigned_hosts))); + WorkloadStatus { + id: Some(workload_id.to_hex()), + desired: WorkloadState::Assigned, + actual: WorkloadState::Assigned, + }, + Some( + workload.assigned_hosts + .iter().map(|id| id.to_hex()).collect()) + ) + ); } // 2. Otherwise call mongodb to get host collection to get hosts that meet the capacity requirements - let host_filter = doc! { - "remaining_capacity.cores": { "$gte": workload.system_specs.capacity.cores }, - "remaining_capacity.memory": { "$gte": workload.system_specs.capacity.memory }, - "remaining_capacity.disk": { "$gte": workload.system_specs.capacity.disk } - }; - let eligible_hosts = self.host_collection.get_many_from(host_filter).await? ; + let eligible_hosts = self.find_hosts_meeting_workload_criteria(workload.clone()).await?; log::debug!("Eligible hosts for new workload. MongodDB Host IDs={:?}", eligible_hosts); - - // 3. Randomly choose host/node - let host = match eligible_hosts.choose(&mut rand::thread_rng()) { - Some(h) => h, - None => { - // todo: Try to get another host up to 5 times, if fails thereafter, return error - let err_msg = format!("Failed to locate an eligible host to support the required workload capacity. Workload={:?}", workload); - return Err(anyhow!(err_msg)); - } - }; + let host = eligible_hosts[0].clone(); // Note: The `_id` is an option because it is only generated upon the intial insertion of a record in // a mongodb collection. This also means that whenever a record is fetched from mongodb, it must have the `_id` feild. @@ -193,7 +243,7 @@ impl WorkloadApi { let host_id = host._id.to_owned().unwrap(); // 4. Update the Workload Collection with the assigned Host ID - let workload_query = doc! { "_id": workload_id.clone() }; + let workload_query = doc! { "_id": workload_id }; let updated_workload = &Workload { assigned_hosts: vec![host_id], ..workload.clone() @@ -208,7 +258,7 @@ impl WorkloadApi { // 5. Update the Host Collection with the assigned Workload ID let host_query = doc! { "_id": host.clone()._id }; let updated_host_doc = to_document(&Host { - assigned_workloads: vec![workload_id.clone()], + assigned_workloads: vec![workload_id.to_hex()], ..host.to_owned() })?; let updated_host_result = self.host_collection.update_one_within(host_query, UpdateModifications::Document(updated_host_doc)).await?; @@ -219,11 +269,14 @@ impl WorkloadApi { Ok(types::ApiResult( WorkloadStatus { - id: Some(workload_id), + id: Some(workload_id.to_hex()), desired: WorkloadState::Assigned, actual: WorkloadState::Assigned, }, - Some(updated_workload.assigned_hosts.to_owned()) + Some( + updated_workload.assigned_hosts.to_owned() + .iter().map(|host| host.to_hex()).collect() + ) )) }, WorkloadState::Error, @@ -231,46 +284,48 @@ impl WorkloadApi { .await) } - // Zeeshan to take a look: // NB: Automatically published by the nats-db-connector + // triggers on mongodb [workload] collection (update) pub async fn handle_db_update(&self, msg: Arc) -> Result { log::debug!("Incoming message for 'WORKLOAD.update'"); let payload_buf = msg.payload.to_vec(); - let workload: schemas::Workload = serde_json::from_slice(&payload_buf)?; - log::trace!("New workload to assign. Workload={:#?}", workload); - - // TODO: ...handle the use case for the update entry change stream - - let success_status = WorkloadStatus { - id: workload._id, - desired: WorkloadState::Running, - actual: WorkloadState::Running, - }; - - Ok(types::ApiResult(success_status, None)) - } - // Zeeshan to take a look: - // NB: Automatically published by the nats-db-connector - pub async fn handle_db_deletion(&self, msg: Arc) -> Result { - log::debug!("Incoming message for 'WORKLOAD.delete'"); - - let payload_buf = msg.payload.to_vec(); let workload: schemas::Workload = serde_json::from_slice(&payload_buf)?; - log::trace!("New workload to assign. Workload={:#?}", workload); - - // TODO: ...handle the use case for the delete entry change stream + log::trace!("Workload to update. Workload={:#?}", workload.clone()); + + // 1. remove workloads from existing hosts + self.host_collection.mongo_error_handler( + self.host_collection.collection.update_many( + doc! {}, + doc!{ "$pull": { "assigned_workloads": workload._id } } + ).await + )?; + log::info!("Remove workload from previous hosts. Workload={:#?}", workload._id); + + if workload.metadata.is_deleted == false { + // 3. add workload to specific hosts + self.host_collection.mongo_error_handler( + self.host_collection.collection.update_one( + doc! { "_id": { "$in": workload.clone().assigned_hosts } }, + doc! { "$push": { "assigned_workloads": workload._id } } + ).await + )?; + log::info!("Added workload to new hosts. Workload={:#?}", workload._id); + } else { + log::info!("Skipping (reason: deleted) - Added workload to new hosts. Workload={:#?}", workload._id); + } let success_status = WorkloadStatus { - id: workload._id, - desired: WorkloadState::Removed, - actual: WorkloadState::Removed, + id: workload._id.map(|oid| oid.to_hex()), + desired: WorkloadState::Updating, + actual: WorkloadState::Updating, }; + log::info!("Workload update successful. Workload={:#?}", workload._id); Ok(types::ApiResult(success_status, None)) } - + // NB: Published by the Hosting Agent whenever the status of a workload changes pub async fn handle_status_update(&self, msg: Arc) -> Result { log::debug!("Incoming message for 'WORKLOAD.read_status_update'"); @@ -278,10 +333,27 @@ impl WorkloadApi { let payload_buf = msg.payload.to_vec(); let workload_status: WorkloadStatus = serde_json::from_slice(&payload_buf)?; log::trace!("Workload status to update. Status={:?}", workload_status); - - // TODO: ...handle the use case for the workload status update + if workload_status.id == None { + return Err(anyhow!("Got a status update for workload without an id!")); + } + let workload_status_id = workload_status.id.clone().unwrap(); + + // todo: handle othercases + if matches!(workload_status.desired, WorkloadState::Removed) { + self.workload_collection.mongo_error_handler( + self.workload_collection.collection.update_one( + doc! { "_id": MongoDbId::parse_str(workload_status_id)? }, + doc! { + "$set": { + "metadata.is_deleted": true, + "deleted_at": Some(DateTime::now()) + } + } + ).await + )?; + } - Ok(types::ApiResult(workload_status, None)) + return Ok(types::ApiResult(workload_status, None)) } /******************************* For Host Agent *********************************/ @@ -297,7 +369,7 @@ impl WorkloadApi { // 2. Respond to endpoint request let status = WorkloadStatus { - id: workload._id, + id: workload._id.map(|oid| oid.to_hex()), desired: WorkloadState::Running, actual: WorkloadState::Unknown("..".to_string()), }; diff --git a/rust/util_libs/src/db/mongodb.rs b/rust/util_libs/src/db/mongodb.rs index ecb579a..e28b7ba 100644 --- a/rust/util_libs/src/db/mongodb.rs +++ b/rust/util_libs/src/db/mongodb.rs @@ -1,4 +1,5 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; +use async_nats::Error; use async_trait::async_trait; use bson::{self, doc, Document}; use futures::stream::TryStreamExt; @@ -21,6 +22,9 @@ pub trait MongoDbAPI where T: Serialize + for<'de> Deserialize<'de> + Unpin + Send + Sync, { + fn mongo_error_handler(&self, result: Result) -> Result; + async fn mongo_cursor_to_list(&self, cursor: mongodb::Cursor) -> Result>; + async fn aggregate(&self, pipeline: Vec) -> Result>; async fn get_one_from(&self, filter: Document) -> Result>; async fn get_many_from(&self, filter: Document) -> Result>; async fn insert_one_into(&self, item: T) -> Result; @@ -39,7 +43,7 @@ pub struct MongoCollection where T: Serialize + for<'de> Deserialize<'de> + Unpin + Send + Sync + Default + IntoIndexes, { - collection: Collection, + pub collection: Collection, indices: Vec, } @@ -90,6 +94,31 @@ impl MongoDbAPI for MongoCollection where T: Serialize + for<'de> Deserialize<'de> + Unpin + Send + Sync + Default + IntoIndexes + Debug, { + + fn mongo_error_handler(&self, result: Result) -> Result + { + let rtn = result.map_err(ServiceError::Database)?; + Ok(rtn) + } + + async fn mongo_cursor_to_list(&self, cursor: mongodb::Cursor) -> Result> { + let results: Vec = cursor.try_collect().await.map_err(ServiceError::Database)?; + return Ok(results); + } + + async fn aggregate(&self, pipeline: Vec) -> Result> { + log::info!("aggregate pipeline {:?}", pipeline); + let cursor = self.collection.aggregate(pipeline).await?; + + let results_doc: Vec = cursor.try_collect().await.map_err(ServiceError::Database)?; + + let results: Vec = results_doc.into_iter().map( + |doc| bson::from_document::(doc).with_context(|| "failed to deserialize document") + ).collect::>>()?; + + Ok(results) + } + async fn get_one_from(&self, filter: Document) -> Result> { log::info!("get_one_from filter {:?}", filter); @@ -245,8 +274,8 @@ mod tests { } use super::*; - use crate::db::schemas::{self, Capacity}; - use bson::{self, doc, oid}; + use crate::db::schemas::{self, Capacity, Metadata}; + use bson::{self, doc, oid, DateTime}; use dotenv::dotenv; #[tokio::test] @@ -267,7 +296,13 @@ mod tests { fn get_mock_host() -> schemas::Host { schemas::Host { - _id: Some(oid::ObjectId::new().to_string()), + _id: Some(oid::ObjectId::new()), + metadata: Metadata { + is_deleted: false, + created_at: Some(DateTime::now()), + updated_at: Some(DateTime::now()), + deleted_at: None, + }, device_id: "Vf3IceiD".to_string(), ip_address: "127.0.0.1".to_string(), remaining_capacity: Capacity { @@ -279,7 +314,7 @@ mod tests { avg_network_speed: 500, avg_latency: 10, assigned_workloads: vec!["workload_id".to_string()], - assigned_hoster: "hoster".to_string(), + assigned_hoster: oid::ObjectId::new(), } } @@ -313,7 +348,7 @@ mod tests { let fetched_hosts = host_api.get_many_from(filter_many.clone()).await?; assert_eq!(fetched_hosts.len(), 3); - let ids: Vec = fetched_hosts.into_iter().map(|h| h._id.unwrap()).collect(); + let ids: Vec = fetched_hosts.into_iter().map(|h| h._id.unwrap_or(oid::ObjectId::new()).to_hex()).collect(); assert!(ids.contains(&ids[0])); assert!(ids.contains(&ids[1])); assert!(ids.contains(&ids[2])); diff --git a/rust/util_libs/src/db/schemas.rs b/rust/util_libs/src/db/schemas.rs index 0a6ff82..a6c3f20 100644 --- a/rust/util_libs/src/db/schemas.rs +++ b/rust/util_libs/src/db/schemas.rs @@ -1,6 +1,6 @@ use super::mongodb::IntoIndexes; use anyhow::Result; -use bson::{self, doc, Document}; +use bson::{self, doc, DateTime, Document}; use mongodb::options::IndexOptions; use semver::{BuildMetadata, Prerelease}; use serde::{Deserialize, Serialize}; @@ -25,52 +25,93 @@ pub use String as DeveloperJWT; pub use String as SemVer; // Providetype Alias for MongoDB ID (mongo's automated id) -pub use String as MongoDbId; +pub use bson::oid::ObjectId as MongoDbId; // ==================== User Schema ==================== #[derive(Serialize, Deserialize, Clone, Debug)] -pub enum Role { - Developer(DeveloperJWT), // jwt string - Host(HosterPubKey), // host pubkey +pub enum UserPermission { + Admin } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct RoleInfo { - pub ref_id: String, // Hoster/Developer Mongodb ID ref - pub role: Role, // *INDEXED* +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct Metadata { + pub is_deleted: bool, + pub deleted_at: Option, + pub updated_at: Option, + pub created_at: Option, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct User { #[serde(skip_serializing_if = "Option::is_none")] pub _id: Option, - pub email: String, + pub metadata: Metadata, pub jurisdiction: String, - pub roles: Vec, + pub permissions: Vec, + pub user_info: Option, + pub developer: Option, + pub host: Option, } +// No Additional Indexing for Developer impl IntoIndexes for User { fn into_indices(self) -> Result)>> { let mut indices = vec![]; - // Add Email Index - let email_index_doc = doc! { "email": 1 }; - let email_index_opts = Some( + // add user_info index + let user_info_index_doc = doc! { "user_info": 1 }; + let user_info_index_opts = Some( IndexOptions::builder() - .unique(true) - .name(Some("email_index".to_string())) + .name(Some("user_info_index".to_string())) .build(), ); - indices.push((email_index_doc, email_index_opts)); + indices.push((user_info_index_doc, user_info_index_opts)); + + // add developer index + let developer_index_doc = doc! { "developer": 1 }; + let developer_index_opts = Some( + IndexOptions::builder() + .name(Some("developer_index".to_string())) + .build(), + ); + indices.push((developer_index_doc, developer_index_opts)); + + // add host index + let host_index_doc = doc! { "host": 1 }; + let host_index_opts = Some( + IndexOptions::builder() + .name(Some("host_index".to_string())) + .build(), + ); + indices.push((host_index_doc, host_index_opts)); - // Add Role Index - let role_index_doc = doc! { "roles.role": 1 }; - let role_index_opts = Some( + Ok(indices) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct UserInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub _id: Option, + pub metadata: Metadata, + pub user: MongoDbId, + pub email: String, + pub given_names: String, + pub family_name: String, +} + +impl IntoIndexes for UserInfo { + fn into_indices(self) -> Result)>> { + let mut indices = vec![]; + + // add email index + let email_index_doc = doc! { "email": 1 }; + let email_index_opts = Some( IndexOptions::builder() - .name(Some("role_index".to_string())) + .name(Some("email_index".to_string())) .build(), ); - indices.push((role_index_doc, role_index_opts)); + indices.push((email_index_doc, email_index_opts)); Ok(indices) } @@ -81,8 +122,9 @@ impl IntoIndexes for User { pub struct Developer { #[serde(skip_serializing_if = "Option::is_none")] pub _id: Option, + pub metadata: Metadata, pub user_id: String, // MongoDB ID ref to `user._id` (which stores the hoster's pubkey, jurisdiction and email) - pub requested_workloads: Vec, // MongoDB ID refs to `workload._id` + pub active_workloads: Vec, // MongoDB ID refs to `workload._id` } // No Additional Indexing for Developer @@ -97,8 +139,9 @@ impl IntoIndexes for Developer { pub struct Hoster { #[serde(skip_serializing_if = "Option::is_none")] pub _id: Option, + pub metadata: Metadata, pub user_id: String, // MongoDB ID ref to `user.id` (which stores the hoster's pubkey, jurisdiction and email) - pub assigned_hosts: Vec, // Auto-generated Nats server IDs + pub assigned_hosts: Vec, // MongoDB ID refs to `host._id` } // No Additional Indexing for Hoster @@ -120,6 +163,8 @@ pub struct Capacity { pub struct Host { #[serde(skip_serializing_if = "Option::is_none")] pub _id: Option, + pub metadata: Metadata, + pub assigned_hoster: MongoDbId, pub device_id: String, // *INDEXED*, Auto-generated Nats server ID pub ip_address: String, pub remaining_capacity: Capacity, @@ -127,7 +172,6 @@ pub struct Host { pub avg_network_speed: i64, pub avg_latency: i64, pub assigned_workloads: Vec, // MongoDB ID refs to `workload._id` - pub assigned_hoster: HosterPubKey, // *INDEXED*, Hoster pubkey } impl IntoIndexes for Host { @@ -157,13 +201,14 @@ pub enum WorkloadState { Running, Removed, Uninstalled, + Updating, Error(String), // String = error message Unknown(String), // String = context message } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WorkloadStatus { - pub id: Option, + pub id: Option, pub desired: WorkloadState, pub actual: WorkloadState, } @@ -179,12 +224,13 @@ pub struct SystemSpecs { pub struct Workload { #[serde(skip_serializing_if = "Option::is_none")] pub _id: Option, + pub metadata: Metadata, + pub assigned_developer: MongoDbId, // *INDEXED*, Developer Mongodb ID pub version: SemVer, pub nix_pkg: String, // (Includes everthing needed to deploy workload - ie: binary & env pkg & deps, etc) - pub assigned_developer: String, // *INDEXED*, Developer Mongodb ID pub min_hosts: u16, pub system_specs: SystemSpecs, - pub assigned_hosts: Vec, // Host Device IDs (eg: assigned nats server id) + pub assigned_hosts: Vec, // Host Device IDs (eg: assigned nats server id) // pub status: WorkloadStatus, } @@ -202,9 +248,15 @@ impl Default for Workload { Self { _id: None, + metadata: Metadata { + is_deleted: false, + created_at: Some(DateTime::now()), + updated_at: Some(DateTime::now()), + deleted_at: None + }, version: semver, nix_pkg: String::new(), - assigned_developer: String::new(), + assigned_developer: MongoDbId::new(), min_hosts: 1, system_specs: SystemSpecs { capacity: Capacity {