diff --git a/.env b/.env new file mode 100644 index 0000000..9ded0c8 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=postgresql://joylink:Joylink@0503@localhost:5432/joylink diff --git a/.vscode/settings.json b/.vscode/settings.json index 0dbc8f0..c28db26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,24 @@ { "cSpell.words": [ + "chrono", "cpus", "Graphi", "graphiql", "hashbrown", "Hasher", + "Iden", + "Iscs", "Joylink", "jsonwebtoken", "mplj", + "plpgsql", "prost", "proto", "protoc", "protos", + "repr", "rtss", + "sqlx", "sysinfo", "thiserror", "timestep", diff --git a/Cargo.lock b/Cargo.lock index f993a47..e0c0e90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,10 +71,59 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.86" +name = "anstream" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "ascii_utils" @@ -227,6 +276,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -342,10 +400,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bevy_app" -version = "0.14.1" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af99549f5de61cc91c8c23303b13aa07f97b73fbace39695dee0a0a32cec9d4" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bevy_app" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5361d0f8a8677a5d0102cfe7321a7ecd2a8b9a4f887ce0dde1059311cf9cd42" dependencies = [ "bevy_derive", "bevy_ecs", @@ -361,9 +425,9 @@ dependencies = [ [[package]] name = "bevy_core" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccc7118a2865267136afb5e6a2c0aed30994e522f298b2ba0b088878e6ddf59" +checksum = "de706862871a1fe99ea619bff2f99d73e43ad82f19ef866a9e19a14c957c8537" dependencies = [ "bevy_app", "bevy_ecs", @@ -375,9 +439,9 @@ dependencies = [ [[package]] name = "bevy_derive" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8675f337f374b2b8ae90539982b947d171f9adb302d00c032b823bd5231f8978" +checksum = "3fbfc33a4c6b80760bb8bf850a2cc65a1e031da62fd3ca8b552189104dc98514" dependencies = [ "bevy_macro_utils", "quote", @@ -386,16 +450,16 @@ dependencies = [ [[package]] name = "bevy_ecs" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3eed7f144811946ebfa1c740da9e3bcd6dd2dd4da844eda085249d29bc9fef" +checksum = "9ee4222406637f3c8e3991a99788cfcde76097bf997c311f1b6297364057483f" dependencies = [ "bevy_ecs_macros", "bevy_ptr", "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags", + "bitflags 2.6.0", "concurrent-queue", "fixedbitset 0.5.7", "nonmax", @@ -405,9 +469,9 @@ dependencies = [ [[package]] name = "bevy_ecs_macros" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d523630f2eb9fde6727e6c5ea48fa708079c5345da21ffeb1a4bd8ca761830da" +checksum = "36b573430b67aff7bde8292257494f39343401379bfbda64035ba4918bba7b20" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -417,9 +481,9 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec4a585ec2a6dedd4f4143c07219d120ae142121929f0d83e68d82a452cdc9b" +checksum = "bfc65e570012e64a21f3546df68591aaede8349e6174fb500071677f54f06630" dependencies = [ "proc-macro2", "quote", @@ -429,15 +493,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ed72afbb6249a6803a3ed7bd2f68ff080d9392f550475e050b34c1e1c1e3e8f" +checksum = "61baa1bdc1f4a7ac2c18217570a7cc04e1cd54d38456e91782f0371c79afe0a8" [[package]] name = "bevy_reflect" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37e8fc3c61d04da480c95cc8c303aa7781afed6be01dae333b336af493c38e" +checksum = "2508785a4a5809f25a237eec4fee2c91a4dbcf81324b2bbc2d6c52629e603781" dependencies = [ "bevy_ptr", "bevy_reflect_derive", @@ -452,9 +516,9 @@ dependencies = [ [[package]] name = "bevy_reflect_derive" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc00d5086f5bf534b4c2dbeba549a6b8d3223515f3cb5ba4fdaabe953ec6cea" +checksum = "967d5da1882ec3bb3675353915d3da909cafac033cbf31e58727824a1ad2a288" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -465,9 +529,9 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f5414c3f49c96e02ceccf5fa12fb6cfbf8b271d2a820902d6f622e9c2fa681" +checksum = "77865f310b1fc48fb05b7c4adbe76607ec01d0c14f8ab4caba4d714c86439946" dependencies = [ "async-executor", "futures-lite", @@ -476,9 +540,9 @@ dependencies = [ [[package]] name = "bevy_time" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3fb18cfac62098e07786e422e84b4f45f469f27ccb5b572b409500bef465f33" +checksum = "f4e4d53ec32a1b16492396951d04de0d2d90e924bf9adcb8d1adacab5ab6c17c" dependencies = [ "bevy_app", "bevy_ecs", @@ -490,14 +554,14 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6efbe5a621b56cc4ffa41074929eca84107e242302496b9bb7550675e6bf2e7" +checksum = "ffb0ec333b5965771153bd746f92ffd8aeeb9d008a8620ffd9ed474859381a5e" dependencies = [ "ahash", "bevy_utils_proc_macros", "getrandom", - "hashbrown", + "hashbrown 0.14.5", "thread_local", "tracing", "web-time", @@ -505,20 +569,29 @@ dependencies = [ [[package]] name = "bevy_utils_proc_macros" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a1e91b4294cad2d08620ac062509395d4f65247b636946d6497eaeccf4dbfd" +checksum = "38f1ab8f2f6f58439d260081d89a42b02690e5fdd64f814edc9417d33fcf2857" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -577,9 +650,55 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -589,6 +708,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -599,6 +738,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -614,6 +788,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -642,12 +831,27 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -699,6 +903,17 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -715,9 +930,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -729,6 +961,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" @@ -739,6 +974,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -765,6 +1012,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "fast_chemail" version = "0.9.6" @@ -792,6 +1061,17 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -814,6 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -822,6 +1103,28 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -924,6 +1227,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -935,6 +1244,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "headers" version = "0.4.0" @@ -971,6 +1289,39 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "1.1.0" @@ -1097,10 +1448,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.13.0" @@ -1125,6 +1482,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -1145,6 +1513,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1152,12 +1523,45 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -1179,6 +1583,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1191,6 +1605,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1235,6 +1655,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonmax" version = "0.5.5" @@ -1270,6 +1700,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1285,6 +1732,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1292,6 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1309,6 +1768,16 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "overload" version = "0.1.1" @@ -1321,6 +1790,41 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "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 = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pem" version = "3.0.4" @@ -1331,6 +1835,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1424,6 +1937,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1579,6 +2119,24 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "regex" version = "1.10.6" @@ -1638,10 +2196,43 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtss_api" version = "0.1.0" dependencies = [ + "anyhow", "async-graphql", "async-graphql-axum", "axum", @@ -1650,6 +2241,8 @@ dependencies = [ "bevy_ecs", "chrono", "jsonwebtoken", + "rtss_db", + "rtss_dto", "rtss_log", "rtss_sim_manage", "rtss_trackside", @@ -1674,13 +2267,22 @@ dependencies = [ [[package]] name = "rtss_db" version = "0.1.0" +dependencies = [ + "anyhow", + "rtss_dto", + "rtss_log", + "sqlx", + "thiserror", +] [[package]] name = "rtss_dto" version = "0.1.0" dependencies = [ + "async-graphql", "prost", "prost-build", + "sqlx", ] [[package]] @@ -1715,8 +2317,14 @@ dependencies = [ name = "rtss_simulation" version = "0.1.0" dependencies = [ + "anyhow", + "clap", + "config", + "enum_dispatch", "rtss_api", + "rtss_db", "rtss_log", + "serde", "tokio", ] @@ -1732,6 +2340,16 @@ dependencies = [ "rtss_log", ] +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1744,7 +2362,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1764,19 +2382,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] -name = "serde" -version = "1.0.208" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1805,6 +2429,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1854,6 +2487,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "simple_asn1" version = "0.6.2" @@ -1880,6 +2523,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smol_str" @@ -1905,6 +2551,231 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", + "uuid", +] [[package]] name = "static_assertions_next" @@ -1912,6 +2783,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1940,6 +2822,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.76" @@ -2051,6 +2939,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2068,9 +2965,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2130,11 +3027,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.20", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2154,6 +3066,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.6.18", ] @@ -2180,7 +3094,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "bytes", "http", "http-body", @@ -2333,6 +3247,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "untrusted" version = "0.9.0" @@ -2356,6 +3288,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.10.0" @@ -2371,6 +3309,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2383,6 +3327,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -2469,6 +3419,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2498,7 +3458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core 0.57.0", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2507,7 +3467,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2519,7 +3479,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2550,7 +3510,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -2559,7 +3528,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2568,7 +3537,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -2577,28 +3561,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2611,24 +3613,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2653,6 +3679,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2673,3 +3708,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 446048f..b64e63b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,29 @@ edition = "2021" members = ["crates/*"] [workspace.dependencies] -bevy_app = "0.14.1" -bevy_core = "0.14.1" -bevy_ecs = "0.14.1" -bevy_time = "0.14.1" -rayon = "1.10.0" -tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] } -thiserror = "1.0.63" +bevy_app = "0.14" +bevy_core = "0.14" +bevy_ecs = "0.14" +bevy_time = "0.14" +rayon = "1.10" +tokio = { version = "1.40", features = ["macros", "rt-multi-thread"] } +thiserror = "1.0" +sqlx = { version = "0.8", features = [ + "runtime-tokio", + "postgres", + "json", + "chrono", +] } +serde = { version = "1.0", features = ["derive"] } +anyhow = "1.0" [dependencies] tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] } rtss_log = { path = "crates/rtss_log" } rtss_api = { path = "crates/rtss_api" } +rtss_db = { path = "crates/rtss_db" } +serde = { workspace = true } +config = "0.14.0" +clap = { version = "4.5", features = ["derive"] } +enum_dispatch = "0.3" +anyhow = { workspace = true } diff --git a/README.md b/README.md index d699a7b..0a2a41f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - TODO Highlight: TODO 高亮 - vscode-icons: 图标优化 - YAML: YAML 文件支持 +- Prettier SQL VSCode: SQL文件格式化 +- vscode-proto3: proto文件支持 ### 安装 pre-commit diff --git a/conf/default.toml b/conf/default.toml new file mode 100644 index 0000000..7ba1be5 --- /dev/null +++ b/conf/default.toml @@ -0,0 +1,7 @@ +[server] +port = 8765 + +[database] + +[log] +level = "debug" diff --git a/conf/dev.toml b/conf/dev.toml new file mode 100644 index 0000000..3f2297a --- /dev/null +++ b/conf/dev.toml @@ -0,0 +1,2 @@ +[database] +url = "postgresql://joylink:Joylink@0503@localhost:5432/joylink" diff --git a/conf/local_test copy.toml b/conf/local_test copy.toml new file mode 100644 index 0000000..d33fcf2 --- /dev/null +++ b/conf/local_test copy.toml @@ -0,0 +1,2 @@ +[database] +url = "postgresql://joylink:Joylink@0503@192.168.33.233:5432/joylink" diff --git a/conf/local_test.toml b/conf/local_test.toml new file mode 100644 index 0000000..eec6adf --- /dev/null +++ b/conf/local_test.toml @@ -0,0 +1,5 @@ +[database] +url = "postgresql://joylink:Joylink@0503@192.168.33.233:5432/joylink" + +[log] +level = "info" diff --git a/crates/rtss_api/Cargo.toml b/crates/rtss_api/Cargo.toml index b4c0e7f..22e1fe5 100644 --- a/crates/rtss_api/Cargo.toml +++ b/crates/rtss_api/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } serde = { version = "1.0.208", features = ["derive"] } serde_json = "1.0.125" @@ -21,3 +22,5 @@ bevy_ecs = { workspace = true } rtss_log = { path = "../rtss_log" } rtss_sim_manage = { path = "../rtss_sim_manage" } rtss_trackside = { path = "../rtss_trackside" } +rtss_db = { path = "../rtss_db" } +rtss_dto = { path = "../rtss_dto" } diff --git a/crates/rtss_api/src/draft_data.rs b/crates/rtss_api/src/draft_data.rs new file mode 100644 index 0000000..25b5927 --- /dev/null +++ b/crates/rtss_api/src/draft_data.rs @@ -0,0 +1,191 @@ +use async_graphql::{Context, InputObject, Object, SimpleObject}; +use chrono::{DateTime, Local}; +use rtss_db::DraftDataAccessor; +use rtss_db::RtssDbAccessor; +use rtss_dto::common::DataType; + +use crate::pagination::PageQueryDto; + +#[derive(Default)] +pub struct DraftDataQuery; + +#[derive(Default)] +pub struct DraftDataMutation; + +#[Object] +impl DraftDataQuery { + /// 分页查询草稿数据 + async fn draft_data_paging<'ctx>( + &self, + ctx: &Context<'ctx>, + paging: PageQueryDto, + query: DraftDataFilterDto, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let paging_result = db_accessor + .query_draft_data(query.into(), paging.into()) + .await?; + Ok(paging_result.into()) + } + /// 根据id获取草稿数据 + async fn draft_data(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor.query_draft_data_by_id(id).await?; + Ok(draft_data.into()) + } + /// 查询是否已经存在同一用户下的同名草稿数据 + async fn draft_data_exist( + &self, + ctx: &Context<'_>, + user_id: i32, + name: String, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let exist = db_accessor.is_draft_data_exist(user_id, &name).await?; + Ok(exist) + } +} + +#[Object] +impl DraftDataMutation { + /// 创建草稿数据 + async fn create_draft_data( + &self, + ctx: &Context<'_>, + input: CreateDraftDataDto, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor.create_draft_data(input.into()).await?; + Ok(draft_data.into()) + } + /// 更新草稿数据name + async fn update_draft_data_name( + &self, + ctx: &Context<'_>, + id: i32, + name: String, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor.update_draft_data_name(id, &name).await?; + Ok(draft_data.into()) + } + /// 更新草稿数据data + async fn update_draft_data_data( + &self, + ctx: &Context<'_>, + id: i32, + data: Vec, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor + .update_draft_data_data(id, data.as_slice()) + .await?; + Ok(draft_data.into()) + } + /// 删除草稿数据 + async fn delete_draft_data( + &self, + ctx: &Context<'_>, + id: Vec, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + db_accessor.delete_draft_data(id.as_slice()).await?; + Ok(true) + } + /// 设置草稿数据的默认发布数据 + async fn set_default_release_data_id( + &self, + ctx: &Context<'_>, + id: i32, + release_data_id: i32, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor + .set_default_release_data_id(id, release_data_id) + .await?; + Ok(draft_data.into()) + } + /// 草稿数据另存为 + async fn save_as_new_draft_data( + &self, + ctx: &Context<'_>, + id: i32, + name: String, + user_id: i32, + ) -> async_graphql::Result { + let db_accessor = ctx.data::()?; + let draft_data = db_accessor.save_as_new_draft(id, &name, user_id).await?; + Ok(draft_data.into()) + } +} + +#[derive(Debug, InputObject)] +pub struct CreateDraftDataDto { + pub name: String, + pub data_type: rtss_dto::common::DataType, + pub user_id: i32, +} + +impl From for rtss_db::CreateDraftData { + fn from(value: CreateDraftDataDto) -> Self { + Self::new(&value.name, value.data_type, value.user_id) + } +} + +/// 草稿数据查询条件 +#[derive(Debug, InputObject)] +pub struct DraftDataFilterDto { + pub user_id: Option, + pub name: Option, + pub data_type: Option, +} + +impl From for rtss_db::DraftDataQuery { + fn from(value: DraftDataFilterDto) -> Self { + Self { + user_id: value.user_id, + name: value.name, + data_type: value.data_type, + } + } +} + +#[derive(Debug, SimpleObject)] +pub struct DraftData { + pub id: i32, + pub name: String, + pub data_type: rtss_dto::common::DataType, + pub data: Option>, + pub user_id: i32, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +impl From for DraftData { + fn from(value: rtss_db::model::DraftDataModel) -> Self { + Self { + id: value.id, + name: value.name, + data_type: DataType::try_from(value.data_type).unwrap(), + data: value.data, + user_id: value.user_id, + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + +#[derive(Debug, SimpleObject)] +pub struct DraftDataPage { + pub total: i64, + pub data: Vec, +} + +impl From> for DraftDataPage { + fn from(value: rtss_db::common::PageResult) -> Self { + Self { + total: value.total, + data: value.data.into_iter().map(|m| m.into()).collect(), + } + } +} diff --git a/crates/rtss_api/src/lib.rs b/crates/rtss_api/src/lib.rs index 9d42c75..9489c4c 100644 --- a/crates/rtss_api/src/lib.rs +++ b/crates/rtss_api/src/lib.rs @@ -1,21 +1,9 @@ +mod draft_data; mod jwt_auth; +mod pagination; mod server; mod simulation; -mod simulation_operation; +mod simulation_definition; mod sys_info; + pub use server::*; - -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/crates/rtss_api/src/pagination.rs b/crates/rtss_api/src/pagination.rs new file mode 100644 index 0000000..31275ee --- /dev/null +++ b/crates/rtss_api/src/pagination.rs @@ -0,0 +1,29 @@ +use async_graphql::{Enum, InputObject, SimpleObject}; + +#[derive(Enum, Copy, Clone, Default, Eq, PartialEq, Debug)] +#[graphql(remote = "rtss_db::common::SortOrder")] +pub enum SortOrder { + #[default] + Asc, + Desc, +} + +#[derive(InputObject, Debug)] +pub struct PageQueryDto { + pub page: i32, + pub items_per_page: i32, +} + +impl From for rtss_db::common::PageQuery { + fn from(value: PageQueryDto) -> Self { + Self { + page: value.page, + items_per_page: value.items_per_page, + } + } +} + +#[derive(SimpleObject)] +pub struct PageDto { + pub total: i64, +} diff --git a/crates/rtss_api/src/server.rs b/crates/rtss_api/src/server.rs index 0e9ca6e..c23d5d3 100644 --- a/crates/rtss_api/src/server.rs +++ b/crates/rtss_api/src/server.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; - use async_graphql::*; use async_graphql::{EmptySubscription, Schema}; use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; @@ -13,31 +11,23 @@ use axum::{ }; use http::{playground_source, GraphQLPlaygroundConfig}; use rtss_log::tracing::{debug, error, info}; -use rtss_sim_manage::SimulationManager; use tokio::net::TcpListener; -use tokio::sync::Mutex; use tower_http::cors::CorsLayer; -use crate::{jwt_auth, simulation}; +use crate::simulation_definition::MutexSimulationManager; +use crate::{draft_data, jwt_auth}; pub struct ServerConfig { + pub database_url: String, pub port: u16, } -impl Default for ServerConfig { - fn default() -> Self { - Self { port: 8080 } - } -} - impl ServerConfig { - pub fn new(port: u16) -> Self { - Self { port } - } - - pub fn with_port(mut self, port: u16) -> Self { - self.port = port; - self + pub fn new(database_url: &str, port: u16) -> Self { + Self { + database_url: database_url.to_string(), + port, + } } pub fn to_socket_addr(&self) -> String { @@ -45,8 +35,8 @@ impl ServerConfig { } } -pub async fn serve(config: ServerConfig) { - let schema = new_schema().await; +pub async fn serve(config: ServerConfig) -> anyhow::Result<()> { + let schema = new_schema(&config).await; let app = Router::new() .route("/", get(graphiql).post(graphql_handler)) @@ -64,8 +54,8 @@ pub async fn serve(config: ServerConfig) { TcpListener::bind(config.to_socket_addr()).await.unwrap(), app, ) - .await - .unwrap(); + .await?; + Ok(()) } async fn graphql_handler( @@ -93,27 +83,15 @@ async fn graphiql() -> impl IntoResponse { pub type SimulationSchema = Schema; #[derive(Default, MergedObject)] -pub struct Query(simulation::SimulationQuery); +pub struct Query(draft_data::DraftDataQuery); #[derive(Default, MergedObject)] -pub struct Mutation(simulation::SimulationMutation); +pub struct Mutation(draft_data::DraftDataMutation); -pub struct MutexSimulationManager(Mutex); -impl Default for MutexSimulationManager { - fn default() -> Self { - Self(Mutex::new(SimulationManager::default())) - } -} -impl Deref for MutexSimulationManager { - type Target = Mutex; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub async fn new_schema() -> SimulationSchema { +pub async fn new_schema(config: &ServerConfig) -> SimulationSchema { + let dba = rtss_db::get_db_accessor(&config.database_url).await; Schema::build(Query::default(), Mutation::default(), EmptySubscription) + .data(dba) .data(MutexSimulationManager::default()) .finish() } diff --git a/crates/rtss_api/src/simulation.rs b/crates/rtss_api/src/simulation.rs index 9b993e7..16697c0 100644 --- a/crates/rtss_api/src/simulation.rs +++ b/crates/rtss_api/src/simulation.rs @@ -2,7 +2,10 @@ use async_graphql::{Context, InputObject, Object}; use rtss_log::tracing::info; use rtss_sim_manage::{AvailablePlugins, SimulationBuilder}; -use crate::{jwt_auth::Claims, simulation_operation::SimulationOperation, MutexSimulationManager}; +use crate::{ + jwt_auth::Claims, + simulation_definition::{MutexSimulationManager, SimulationOperation}, +}; #[derive(Default)] pub struct SimulationQuery; diff --git a/crates/rtss_api/src/simulation_operation.rs b/crates/rtss_api/src/simulation_definition.rs similarity index 53% rename from crates/rtss_api/src/simulation_operation.rs rename to crates/rtss_api/src/simulation_definition.rs index 0f82edc..30fdc16 100644 --- a/crates/rtss_api/src/simulation_operation.rs +++ b/crates/rtss_api/src/simulation_definition.rs @@ -1,5 +1,23 @@ +use std::ops::Deref; + use async_graphql::Enum; use bevy_ecs::event::Event; +use rtss_sim_manage::SimulationManager; +use tokio::sync::Mutex; + +pub struct MutexSimulationManager(Mutex); +impl Default for MutexSimulationManager { + fn default() -> Self { + Self(Mutex::new(SimulationManager::default())) + } +} +impl Deref for MutexSimulationManager { + type Target = Mutex; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} #[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)] pub enum SimulationOperation { diff --git a/crates/rtss_db/Cargo.toml b/crates/rtss_db/Cargo.toml index 0da5b8e..946e724 100644 --- a/crates/rtss_db/Cargo.toml +++ b/crates/rtss_db/Cargo.toml @@ -4,3 +4,17 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = { workspace = true } +sqlx = { workspace = true, features = [ + "runtime-tokio", + "macros", + "chrono", + "json", + "derive", + "postgres", + "uuid", +] } +thiserror = { workspace = true } + +rtss_dto = { path = "../rtss_dto" } +rtss_log = { path = "../rtss_log" } diff --git a/crates/rtss_db/src/common.rs b/crates/rtss_db/src/common.rs new file mode 100644 index 0000000..4d1e00d --- /dev/null +++ b/crates/rtss_db/src/common.rs @@ -0,0 +1,160 @@ +use std::fmt::Display; + +#[derive(Debug, Clone)] +pub struct PageQuery { + pub page: i32, + pub items_per_page: i32, +} + +impl Default for PageQuery { + fn default() -> Self { + Self { + page: DEFAULT_PAGE, + items_per_page: DEFAULT_ITEMS_PER_PAGE, + } + } +} + +const DEFAULT_PAGE: i32 = 1; +const DEFAULT_ITEMS_PER_PAGE: i32 = 10; + +impl PageQuery { + pub fn new(page: i32, items_per_page: i32) -> Self { + Self { + page, + items_per_page, + } + } + + pub(crate) fn to_limit_clause(&self) -> String { + format!( + "LIMIT {} OFFSET {}", + self.items_per_page, + (self.page - 1) * self.items_per_page + ) + } +} + +pub struct TableFilterClause { + pub where_clause: String, + pub params: Vec, +} + +/// Trait for table filter +pub trait TableFilter { + fn to_where_clause(&self) -> String; +} + +pub trait TableColumn { + fn name(&self) -> &str; +} + +impl TableColumn for &str { + fn name(&self) -> &str { + self + } +} + +pub fn to_sort_by_clause(sorts: Vec>) -> String +where + T: TableColumn, +{ + if sorts.is_empty() { + return "".to_string(); + } + let sorts: Vec = sorts + .iter() + .map(|s| format!("{} {}", s.field.name(), s.order)) + .collect(); + format!("ORDER BY {}", sorts.join(", ")) +} + +pub struct Sort +where + T: TableColumn, +{ + pub field: T, + pub order: SortOrder, +} + +impl Sort +where + T: TableColumn, +{ + pub fn new(field: T, order: SortOrder) -> Self { + Self { field, order } + } + + pub fn to_order_by_clause(&self) -> String { + format!("ORDER BY {} {}", self.field.name(), self.order) + } +} + +#[derive(Copy, Clone, Default, Eq, PartialEq, Debug)] +pub enum SortOrder { + #[default] + Asc, + Desc, +} + +impl Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Asc => write!(f, "ASC"), + Self::Desc => write!(f, "DESC"), + } + } +} + +#[derive(Debug)] +pub struct PageResult { + pub total: i64, + pub data: Vec, +} + +impl PageResult { + pub fn new(total: i64, data: Vec) -> Self { + Self { total, data } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_page_query_to_limit_clause() { + let page_query = PageQuery { + page: 1, + items_per_page: 10, + }; + assert_eq!(page_query.to_limit_clause(), "LIMIT 10 OFFSET 0"); + + let page_query = PageQuery { + page: 2, + items_per_page: 10, + }; + assert_eq!(page_query.to_limit_clause(), "LIMIT 10 OFFSET 10"); + + let page_query = PageQuery { + page: 3, + items_per_page: 10, + }; + assert_eq!(page_query.to_limit_clause(), "LIMIT 10 OFFSET 20"); + } + + #[test] + fn test_to_sort_by_clause() { + let sorts = vec![ + Sort { + field: "id", + order: SortOrder::Asc, + }, + Sort { + field: "name", + order: SortOrder::Desc, + }, + ]; + assert_eq!(to_sort_by_clause(sorts), "ORDER BY id ASC, name DESC"); + } +} diff --git a/crates/rtss_db/src/db_access/draft_data.rs b/crates/rtss_db/src/db_access/draft_data.rs new file mode 100644 index 0000000..037d248 --- /dev/null +++ b/crates/rtss_db/src/db_access/draft_data.rs @@ -0,0 +1,457 @@ +use std::vec; + +use rtss_dto::common::DataType; +use rtss_log::tracing::debug; + +use crate::{ + common::{PageQuery, PageResult, Sort, SortOrder, TableColumn}, + model::{DraftDataColumn, DraftDataModel}, + DbAccessError, +}; + +use super::RtssDbAccessor; + +/// 草稿数据管理 +#[allow(async_fn_in_trait)] +pub trait DraftDataAccessor { + /// 查询用户草稿数据 + async fn query_draft_data( + &self, + query: DraftDataQuery, + page: PageQuery, + ) -> Result, DbAccessError>; + /// 根据id查询草稿数据 + async fn query_draft_data_by_id(&self, id: i32) -> Result; + /// 是否user_id+name的数据已存在 + async fn is_draft_data_exist(&self, user_id: i32, name: &str) -> Result; + /// 创建草稿数据基本信息 + async fn create_draft_data( + &self, + create: CreateDraftData, + ) -> Result; + /// 更新草稿数据名称 + async fn update_draft_data_name( + &self, + id: i32, + name: &str, + ) -> Result; + /// 更新草稿数据数据 + async fn update_draft_data_data( + &self, + id: i32, + data: &[u8], + ) -> Result; + /// 删除草稿数据 + async fn delete_draft_data(&self, id: &[i32]) -> Result<(), DbAccessError>; + /// 设置默认发布数据id + async fn set_default_release_data_id( + &self, + draft_id: i32, + release_data_id: i32, + ) -> Result; + /// 草稿数据另存为新草稿数据 + async fn save_as_new_draft( + &self, + draft_id: i32, + name: &str, + user_id: i32, + ) -> Result; +} + +#[derive(Debug, Default)] +pub struct DraftDataQuery { + pub user_id: Option, + pub name: Option, + pub data_type: Option, +} + +impl DraftDataQuery { + pub fn with_user_id(mut self, user_id: i32) -> Self { + self.user_id = Some(user_id); + self + } + + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn with_data_type(mut self, data_type: DataType) -> Self { + self.data_type = Some(data_type); + self + } + + fn build_filter(&self) -> String { + let mut filters = vec![]; + if let Some(user_id) = self.user_id { + filters.push(format!("{} = {}", DraftDataColumn::UserId.name(), user_id)); + } + if let Some(name) = &self.name { + filters.push(format!( + "{} LIKE '%{}%'", + DraftDataColumn::Name.name(), + name + )); + } + if let Some(data_type) = self.data_type { + filters.push(format!( + "{} = {}", + DraftDataColumn::DataType.name(), + data_type as i32 + )); + } + if filters.is_empty() { + "".to_string() + } else { + format!("WHERE {}", filters.join(" AND ")) + } + } +} + +pub struct CreateDraftData { + name: String, + data_type: DataType, + data: Option>, + default_release_data_id: Option, + user_id: i32, +} + +impl CreateDraftData { + pub fn new(name: &str, data_type: DataType, user_id: i32) -> Self { + CreateDraftData { + name: name.to_string(), + data_type, + data: None, + default_release_data_id: None, + user_id, + } + } + + pub fn with_data(mut self, data: &[u8]) -> Self { + self.data = Some(data.to_vec()); + self + } + + pub fn with_default_release_data_id(mut self, default_release_data_id: i32) -> Self { + self.default_release_data_id = Some(default_release_data_id); + self + } +} + +impl DraftDataAccessor for RtssDbAccessor { + async fn query_draft_data( + &self, + query: DraftDataQuery, + page: PageQuery, + ) -> Result, DbAccessError> { + let table = DraftDataColumn::Table.name(); + let where_clause = query.build_filter(); + let sql = format!("SELECT COUNT(*) FROM {table} {where_clause}"); + + // log sql + debug!("count sql: {}", sql); + let total: i64 = sqlx::query_scalar(&sql).fetch_one(&self.pool).await?; + + if total == 0 { + return Ok(PageResult::new(total, vec![])); + } + + let select_columns = format!( + "{}, {}, {}, {}, {}, {}", + DraftDataColumn::Id.name(), + DraftDataColumn::Name.name(), + DraftDataColumn::DataType.name(), + DraftDataColumn::UserId.name(), + DraftDataColumn::CreatedAt.name(), + DraftDataColumn::UpdatedAt.name(), + ); + let sort = Sort::new(DraftDataColumn::UpdatedAt, SortOrder::Desc); + let order_by = sort.to_order_by_clause(); + let limit_clause = page.to_limit_clause(); + let paging_sql = format!( + "SELECT {select_columns} FROM {table} {where_clause} {order_by} {limit_clause}", + ); + // log sql + debug!("paging sql: {}", paging_sql); + let list: Vec = sqlx::query_as(&paging_sql).fetch_all(&self.pool).await?; + + Ok(PageResult::new(total, list)) + } + + async fn query_draft_data_by_id(&self, id: i32) -> Result { + let table = DraftDataColumn::Table.name(); + let sql = format!("SELECT * FROM {table} WHERE id = {id}"); + let draft_data: DraftDataModel = sqlx::query_as(&sql).fetch_one(&self.pool).await?; + + Ok(draft_data) + } + + async fn is_draft_data_exist(&self, user_id: i32, name: &str) -> Result { + let table = DraftDataColumn::Table.name(); + let filter = format!( + "WHERE {} = '{}' AND {} = {}", + DraftDataColumn::Name.name(), + name, + DraftDataColumn::UserId.name(), + user_id + ); + let sql = format!("SELECT COUNT(*) FROM {table} {filter}"); + // log sql + debug!("draft data exist check sql: {}", sql); + let count: i64 = sqlx::query_scalar(&sql).fetch_one(&self.pool).await?; + + Ok(count > 0) + } + + async fn create_draft_data( + &self, + create: CreateDraftData, + ) -> Result { + // 检查是否已存在 + let exist = self + .is_draft_data_exist(create.user_id, &create.name) + .await?; + if exist { + return Err(DbAccessError::RowExist); + } + // 插入数据 + let table = DraftDataColumn::Table.name(); + let columns = format!( + "{}, {}, {}, {}, {}", + DraftDataColumn::Name.name(), + DraftDataColumn::DataType.name(), + DraftDataColumn::UserId.name(), + DraftDataColumn::Data.name(), + DraftDataColumn::DefaultReleaseDataId.name(), + ); + let sql = + format!("INSERT INTO {table} ({columns}) VALUES ($1, $2, $3, $4, $5) RETURNING *",); + // log sql + debug!("create sql: {}", sql); + // 插入数据 + let draft_data: DraftDataModel = sqlx::query_as(&sql) + .bind(create.name) + .bind(create.data_type as i32) + .bind(create.user_id) + .bind(create.data) + .bind(create.default_release_data_id) + .fetch_one(&self.pool) + .await?; + + Ok(draft_data) + } + + async fn update_draft_data_name( + &self, + id: i32, + name: &str, + ) -> Result { + let table = DraftDataColumn::Table.name(); + let name_column = DraftDataColumn::Name.name(); + let updated_at_column = DraftDataColumn::UpdatedAt.name(); + let id_column = DraftDataColumn::Id.name(); + let sql = format!("UPDATE {table} SET {name_column} = $1, {updated_at_column} = 'now()' WHERE {id_column} = $2 RETURNING *",); + // log sql + debug!("update name sql: {}", sql); + let draft_data = sqlx::query_as(&sql) + .bind(name) + .bind(id) + .fetch_one(&self.pool) + .await?; + + Ok(draft_data) + } + + async fn update_draft_data_data( + &self, + id: i32, + data: &[u8], + ) -> Result { + let table = DraftDataColumn::Table.name(); + let data_column = DraftDataColumn::Data.name(); + let updated_at_column = DraftDataColumn::UpdatedAt.name(); + let id_column = DraftDataColumn::Id.name(); + let sql = format!("UPDATE {table} SET {data_column} = $1, {updated_at_column} = 'now()' WHERE {id_column} = $2 RETURNING *",); + // log sql + debug!("update data sql: {}", sql); + let draft_data = sqlx::query_as(&sql) + .bind(data) + .bind(id) + .fetch_one(&self.pool) + .await?; + + Ok(draft_data) + } + + async fn delete_draft_data(&self, ids: &[i32]) -> Result<(), DbAccessError> { + let table = DraftDataColumn::Table.name(); + let id_column = DraftDataColumn::Id.name(); + let sql = format!("DELETE FROM {table} WHERE {id_column} = ANY($1)"); + // log sql + debug!("delete sql: {}", sql); + sqlx::query(&sql).bind(ids).execute(&self.pool).await?; + + Ok(()) + } + + async fn set_default_release_data_id( + &self, + draft_id: i32, + release_data_id: i32, + ) -> Result { + let table = DraftDataColumn::Table.name(); + let draft_id_column = DraftDataColumn::Id.name(); + let release_data_id_column = DraftDataColumn::DefaultReleaseDataId.name(); + let sql = format!( + "UPDATE {table} SET {release_data_id_column} = $1 WHERE {draft_id_column} = $2 RETURNING *", + ); + // log sql + debug!("set default release data id sql: {}", sql); + let draft_data = sqlx::query_as(&sql) + .bind(release_data_id) + .bind(draft_id) + .fetch_one(&self.pool) + .await?; + + Ok(draft_data) + } + + async fn save_as_new_draft( + &self, + draft_id: i32, + name: &str, + user_id: i32, + ) -> Result { + let draft_data = self.query_draft_data_by_id(draft_id).await?; + let create = CreateDraftData::new( + name, + DataType::try_from(draft_data.data_type).unwrap(), + user_id, + ) + .with_data(draft_data.data.as_ref().unwrap()); + self.create_draft_data(create).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rtss_log::tracing::Level; + use sqlx::PgPool; + + // You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here. + #[sqlx::test(migrator = "crate::MIGRATOR")] + async fn basic_use_test(pool: PgPool) -> Result<(), DbAccessError> { + rtss_log::Logging::default().with_level(Level::DEBUG).init(); + let accessor = crate::db_access::RtssDbAccessor::new(pool); + // 创建草稿数据测试 + let res = accessor + .create_draft_data(CreateDraftData::new("test", DataType::Em, 10)) + .await?; + + println!("res: {:?}", res); + println!("res.created_at: {:?}", res.created_at.naive_local()); + assert!(res.id > 0); + + // 重复创建测试 + let repeat_create_result = accessor + .create_draft_data(CreateDraftData::new("test", DataType::Em, 10)) + .await; + if let Some(e) = repeat_create_result.err() { + match e { + DbAccessError::RowExist => { + println!("repeat create test pass"); + } + _ => { + panic!("unexpected error: {:?}", e); + } + } + } + + // query by id测试 + let get_by_id = accessor.query_draft_data_by_id(res.id).await; + assert!(get_by_id.is_ok() && get_by_id.unwrap().name.eq("test")); + + // update name测试 + let get_by_id = accessor + .update_draft_data_name(res.id, "test update") + .await?; + assert!(get_by_id.name.eq("test update")); + + // update data测试 + let data = "tests".as_bytes(); + let get_by_id = accessor.update_draft_data_data(res.id, data).await?; + println!("{:?}", get_by_id); + assert!(get_by_id.data.unwrap() == data); + + // save as new draft测试 + let new_draft = accessor.save_as_new_draft(res.id, "new draft", 11).await?; + assert_eq!(new_draft.name, "new draft"); + assert_eq!(new_draft.user_id, 11); + assert_eq!(new_draft.data.unwrap(), data); + assert!(new_draft.updated_at > new_draft.created_at); + + // delete测试 + accessor.delete_draft_data(&[res.id, new_draft.id]).await?; + let get_by_id = accessor.query_draft_data_by_id(res.id).await; + if let Some(e) = get_by_id.err() { + match e { + DbAccessError::SqlxError(sqlx::Error::RowNotFound) => { + println!("delete test pass"); + } + _ => { + panic!("delete test error: {:?}", e); + } + } + } + + // 查询确认当前数据已删除 + let page = accessor + .query_draft_data( + DraftDataQuery { + user_id: None, + name: None, + data_type: None, + }, + PageQuery::new(1, 100), + ) + .await?; + assert_eq!(page.total, 0); + + // 分页查询测试 + // 分四个user_id各插入5条数据 + for i in 1..5 { + for j in 1..6 { + accessor + .create_draft_data(CreateDraftData::new(&format!("test{}", j), DataType::Em, i)) + .await?; + } + } + + let page = accessor + .query_draft_data( + DraftDataQuery { + user_id: Some(1), + name: Some("test".to_string()), + data_type: Some(DataType::Em), + }, + PageQuery::new(1, 10), + ) + .await?; + assert_eq!(page.total, 5); + + let page = accessor + .query_draft_data( + DraftDataQuery { + user_id: None, + name: None, + data_type: None, + }, + PageQuery::new(1, 100), + ) + .await?; + assert_eq!(page.total, 20); + + Ok(()) + } +} diff --git a/crates/rtss_db/src/db_access/mod.rs b/crates/rtss_db/src/db_access/mod.rs new file mode 100644 index 0000000..dd8a81d --- /dev/null +++ b/crates/rtss_db/src/db_access/mod.rs @@ -0,0 +1,19 @@ +mod draft_data; +pub use draft_data::*; +mod release_data; +pub use release_data::*; + +pub struct RtssDbAccessor { + pool: sqlx::PgPool, +} + +impl RtssDbAccessor { + pub fn new(pool: sqlx::PgPool) -> Self { + RtssDbAccessor { pool } + } +} + +pub async fn get_db_accessor(url: &str) -> RtssDbAccessor { + let pool = sqlx::PgPool::connect(url).await.expect("连接数据库失败"); + RtssDbAccessor::new(pool) +} diff --git a/crates/rtss_db/src/db_access/release_data.rs b/crates/rtss_db/src/db_access/release_data.rs new file mode 100644 index 0000000..657266c --- /dev/null +++ b/crates/rtss_db/src/db_access/release_data.rs @@ -0,0 +1,702 @@ +use rtss_dto::common::DataType; +use sqlx::types::chrono; + +use crate::{ + common::{PageQuery, PageResult, Sort, SortOrder, TableColumn}, + model::{ + DraftDataModel, ReleaseDataColumn, ReleaseDataModel, ReleaseDataVersionColumn, + ReleaseDataVersionModel, + }, + DbAccessError, +}; + +use super::{CreateDraftData, DraftDataAccessor, RtssDbAccessor}; + +#[allow(async_fn_in_trait)] +pub trait ReleaseDataAccessor { + /// 从草稿发布新release data和version,并使用新version + async fn release_new_from_draft( + &self, + draft_id: i32, + name: &str, + description: &str, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; + /// 从草稿发布到已有草稿默认的release data的新version,并使用新version + async fn release_to_existing( + &self, + draft_id: i32, + description: &str, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; + /// 分页查询发布数据列表 + async fn query_release_data_list( + &self, + query: ReleaseDataQuery, + page: PageQuery, + ) -> Result, DbAccessError>; + /// 检查name是否存在 + async fn is_release_data_name_exist(&self, name: &str) -> Result; + /// 查询发布数据 + async fn query_release_data_by_id( + &self, + release_id: i32, + ) -> Result; + /// 查询发布数据所有版本信息 + async fn query_release_data_versions( + &self, + release_id: i32, + ) -> Result, DbAccessError>; + /// 根据id查询发布版本数据 + async fn query_release_data_version_by_id( + &self, + version_id: i32, + ) -> Result; + /// 查询发布数据详情 + async fn query_release_data_with_used_version( + &self, + release_id: i32, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; + /// 更新发布数据名称 + async fn update_release_data_name( + &self, + release_id: i32, + name: &str, + ) -> Result; + /// 上架/下架发布数据 + async fn update_release_data_published( + &self, + release_id: i32, + is_published: bool, + ) -> Result; + /// 设置在使用的版本 + async fn set_used_version( + &self, + release_id: i32, + version_id: i32, + ) -> Result; + /// 从指定的版本数据创建草稿 + async fn create_draft_from_version( + &self, + version_id: i32, + user_id: i32, + ) -> Result; +} + +/// 草稿发布结果 +pub struct ReleaseFromDraftResult { + pub release_id: i32, + pub version_id: i32, +} + +/// 发布数据查询条件 +pub struct ReleaseDataQuery { + pub name: Option, + pub user_id: Option, + pub data_type: Option, + pub is_published: Option, +} + +impl Default for ReleaseDataQuery { + fn default() -> Self { + Self::new() + } +} + +impl ReleaseDataQuery { + pub fn new() -> Self { + Self { + name: None, + user_id: None, + data_type: None, + is_published: None, + } + } + + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn with_user_id(mut self, user_id: i32) -> Self { + self.user_id = Some(user_id); + self + } + + pub fn with_data_type(mut self, data_type: rtss_dto::common::DataType) -> Self { + self.data_type = Some(data_type); + self + } + + pub fn with_is_published(mut self, is_published: bool) -> Self { + self.is_published = Some(is_published); + self + } + + pub fn build_filter(&self) -> String { + let mut filters = vec![]; + let rd_name = ReleaseDataColumn::Name.name(); + let rd_user_id = ReleaseDataColumn::UserId.name(); + let rd_data_type = ReleaseDataColumn::DataType.name(); + let rd_is_published = ReleaseDataColumn::IsPublished.name(); + if let Some(name) = &self.name { + filters.push(format!("{rd_name} LIKE '%{name}%'")); + } + if let Some(user_id) = self.user_id { + filters.push(format!("{rd_user_id} = {user_id}")); + } + if let Some(data_type) = self.data_type { + filters.push(format!("{rd_data_type} = {}", data_type as i32)); + } + if let Some(is_published) = self.is_published { + filters.push(format!("{rd_is_published} = {is_published}")); + } + if filters.is_empty() { + "".to_string() + } else { + format!("WHERE {}", filters.join(" AND ")) + } + } +} + +impl ReleaseDataAccessor for RtssDbAccessor { + async fn release_new_from_draft( + &self, + draft_id: i32, + name: &str, + description: &str, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError> { + // 判断发布数据名称是否已存在 + if self.is_release_data_name_exist(name).await? { + return Err(DbAccessError::DataError("发布数据名称已存在".to_string())); + } + // 开启事务 + let mut tx = self.pool.begin().await?; + // 查询草稿数据 + let draft = self.query_draft_data_by_id(draft_id).await?; + // 创建发布数据 + let rd_table = ReleaseDataColumn::Table.name(); + let rd_insert_columns = format!( + "({}, {}, {})", + ReleaseDataColumn::Name.name(), + ReleaseDataColumn::DataType.name(), + ReleaseDataColumn::UserId.name(), + ); + let rd_insert_clause = + format!("INSERT INTO {rd_table} {rd_insert_columns} VALUES ($1, $2, $3) RETURNING *"); + let mut rd = sqlx::query_as::<_, ReleaseDataModel>(&rd_insert_clause) + .bind(name) + .bind(draft.data_type as i32) + .bind(draft.user_id) + .fetch_one(&mut *tx) + .await?; + // 创建发布数据版本 + let rdv_table = ReleaseDataVersionColumn::Table.name(); + let rdv_insert_columns = format!( + "{}, {}, {}, {}", + ReleaseDataVersionColumn::ReleaseDataId.name(), + ReleaseDataVersionColumn::Data.name(), + ReleaseDataVersionColumn::Description.name(), + ReleaseDataVersionColumn::UserId.name(), + ); + let rdv_insert_clause = format!( + "INSERT INTO {rdv_table} ({rdv_insert_columns}) VALUES ($1, $2, $3, $4) RETURNING *" + ); + let rdv = sqlx::query_as::<_, ReleaseDataVersionModel>(&rdv_insert_clause) + .bind(rd.id) + .bind(draft.data) + .bind(description) + .bind(draft.user_id) + .fetch_one(&mut *tx) + .await?; + // 更新发布数据使用的版本 + let used_version_id = ReleaseDataColumn::UsedVersionId.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_update_clause = + format!("UPDATE {rd_table} SET {used_version_id} = $1 WHERE {rd_id} = $2"); + sqlx::query(&rd_update_clause) + .bind(rdv.id) + .bind(rd.id) + .execute(&mut *tx) + .await?; + // 成功后提交事务 + tx.commit().await?; + // 更新草稿数据的默认发布数据id + self.set_default_release_data_id(draft_id, rd.id).await?; + rd.used_version_id = Some(rdv.id); + Ok((rd, rdv)) + } + + async fn release_to_existing( + &self, + draft_id: i32, + description: &str, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError> { + // 查询草稿数据 + let draft = self.query_draft_data_by_id(draft_id).await?; + // 判断草稿是否设置了默认发布数据 + if draft.default_release_data_id.is_none() { + return Err(DbAccessError::DataError( + "草稿未设置默认发布数据".to_string(), + )); + } + // 查询默认发布数据 + let mut rd = self + .query_release_data_by_id(draft.default_release_data_id.unwrap()) + .await?; + // 开启事务 + let mut tx = self.pool.begin().await?; + // 创建发布数据版本 + let rdv_table = ReleaseDataVersionColumn::Table.name(); + let rdv_insert_columns = format!( + "{}, {}, {}, {}", + ReleaseDataVersionColumn::ReleaseDataId.name(), + ReleaseDataVersionColumn::Data.name(), + ReleaseDataVersionColumn::Description.name(), + ReleaseDataVersionColumn::UserId.name(), + ); + let rdv_insert_clause = format!( + "INSERT INTO {rdv_table} ({rdv_insert_columns}) VALUES ($1, $2, $3, $4) RETURNING *" + ); + let rdv = sqlx::query_as::<_, ReleaseDataVersionModel>(&rdv_insert_clause) + .bind(rd.id) + .bind(draft.data) + .bind(description) + .bind(draft.user_id) + .fetch_one(&mut *tx) + .await?; + // 更新发布数据使用的版本 + let rd_table = ReleaseDataColumn::Table.name(); + let used_version_id = ReleaseDataColumn::UsedVersionId.name(); + let rd_updated_at = ReleaseDataColumn::UpdatedAt.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_update_clause = + format!("UPDATE {rd_table} SET {used_version_id} = $1, {rd_updated_at} = 'now()' WHERE {rd_id} = $2"); + sqlx::query(&rd_update_clause) + .bind(rdv.id) + .bind(rd.id) + .execute(&mut *tx) + .await?; + // 成功后提交事务 + tx.commit().await?; + rd.used_version_id = Some(rdv.id); + Ok((rd, rdv)) + } + + async fn query_release_data_list( + &self, + query: ReleaseDataQuery, + page: PageQuery, + ) -> Result, DbAccessError> { + let rd_table = ReleaseDataColumn::Table.name(); + let where_clause = query.build_filter(); + let count_clause = format!("SELECT COUNT(*) FROM {rd_table} {where_clause}"); + + // 查询总数 + let total = sqlx::query_scalar(&count_clause) + .fetch_one(&self.pool) + .await?; + if total == 0 { + return Ok(PageResult::new(0, vec![])); + } + let paging_clause = page.to_limit_clause(); + let order_by = Sort::new(ReleaseDataColumn::UpdatedAt, SortOrder::Desc); + let order_by_clause = order_by.to_order_by_clause(); + let query_clause = + format!("SELECT * FROM {rd_table} {where_clause} {order_by_clause} {paging_clause}",); + let data = sqlx::query_as::<_, ReleaseDataModel>(&query_clause) + .fetch_all(&self.pool) + .await?; + Ok(PageResult::new(total, data)) + } + + async fn is_release_data_name_exist(&self, name: &str) -> Result { + let rd_table = ReleaseDataColumn::Table.name(); + let rd_name = ReleaseDataColumn::Name.name(); + let rd_query_clause = format!( + "SELECT COUNT(*) FROM {rd_table} WHERE {rd_name} = $1", + rd_table = rd_table, + rd_name = rd_name + ); + let count: i64 = sqlx::query_scalar(&rd_query_clause) + .bind(name) + .fetch_one(&self.pool) + .await?; + Ok(count > 0) + } + + async fn query_release_data_by_id( + &self, + release_id: i32, + ) -> Result { + // 查询发布数据 + let rd_table = ReleaseDataColumn::Table.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_query_clause = format!( + "SELECT * FROM {rd_table} WHERE {rd_id} = $1", + rd_table = rd_table, + rd_id = rd_id + ); + let rd = sqlx::query_as::<_, ReleaseDataModel>(&rd_query_clause) + .bind(release_id) + .fetch_one(&self.pool) + .await?; + Ok(rd) + } + + async fn query_release_data_versions( + &self, + release_id: i32, + ) -> Result, DbAccessError> { + // 查询发布数据版本 + let rdv_table = ReleaseDataVersionColumn::Table.name(); + // 查询列,除了data列 + let rdv_columns = format!( + "{}, {}, {}, {}, {}, {}", + ReleaseDataVersionColumn::Id.name(), + ReleaseDataVersionColumn::ReleaseDataId.name(), + ReleaseDataVersionColumn::Version.name(), + ReleaseDataVersionColumn::Description.name(), + ReleaseDataVersionColumn::UserId.name(), + ReleaseDataVersionColumn::CreatedAt.name(), + ); + let rdv_release_id = ReleaseDataVersionColumn::ReleaseDataId.name(); + // 按版本号倒序排序 + let sort = Sort::new(ReleaseDataVersionColumn::Version, SortOrder::Desc); + let order_by_clause = sort.to_order_by_clause(); + let rdv_query_clause = format!( + "SELECT {rdv_columns} FROM {rdv_table} WHERE {rdv_release_id} = $1 {order_by_clause}", + ); + let rdv = sqlx::query_as::<_, ReleaseDataVersionModel>(&rdv_query_clause) + .bind(release_id) + .fetch_all(&self.pool) + .await?; + Ok(rdv) + } + + async fn query_release_data_version_by_id( + &self, + version_id: i32, + ) -> Result { + // 查询发布数据版本 + let rdv_table = ReleaseDataVersionColumn::Table.name(); + let rdv_id = ReleaseDataVersionColumn::Id.name(); + let rdv_query_clause = format!( + "SELECT * FROM {rdv_table} WHERE {rdv_id} = $1", + rdv_table = rdv_table, + rdv_id = rdv_id + ); + let rdv = sqlx::query_as::<_, ReleaseDataVersionModel>(&rdv_query_clause) + .bind(version_id) + .fetch_one(&self.pool) + .await?; + Ok(rdv) + } + + async fn query_release_data_with_used_version( + &self, + release_id: i32, + ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError> { + // 查询发布数据 + let rd = self.query_release_data_by_id(release_id).await?; + if let Some(version_id) = rd.used_version_id { + // 查询发布数据使用的版本 + let rdv = self.query_release_data_version_by_id(version_id).await?; + return Ok((rd, rdv)); + } + Err(DbAccessError::DataError( + "发布数据未设置使用的版本".to_string(), + )) + } + + async fn update_release_data_name( + &self, + release_id: i32, + name: &str, + ) -> Result { + let rd_table = ReleaseDataColumn::Table.name(); + let rd_name = ReleaseDataColumn::Name.name(); + let rd_updated_at = ReleaseDataColumn::UpdatedAt.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_update_clause = format!( + "UPDATE {rd_table} SET {rd_name} = $1, {rd_updated_at} = 'now()' WHERE {rd_id} = $2 RETURNING *", + rd_table = rd_table, + rd_name = rd_name, + rd_id = rd_id + ); + let rd = sqlx::query_as(&rd_update_clause) + .bind(name) + .bind(release_id) + .fetch_one(&self.pool) + .await?; + Ok(rd) + } + + async fn update_release_data_published( + &self, + release_id: i32, + is_published: bool, + ) -> Result { + let rd_table = ReleaseDataColumn::Table.name(); + let rd_is_published = ReleaseDataColumn::IsPublished.name(); + let rd_updated_at = ReleaseDataColumn::UpdatedAt.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_update_clause = format!( + "UPDATE {rd_table} SET {rd_is_published} = $1, {rd_updated_at} = 'now()' WHERE {rd_id} = $2 RETURNING *", + rd_table = rd_table, + rd_is_published = rd_is_published, + rd_id = rd_id + ); + let rd = sqlx::query_as(&rd_update_clause) + .bind(is_published) + .bind(release_id) + .fetch_one(&self.pool) + .await?; + Ok(rd) + } + + async fn set_used_version( + &self, + release_id: i32, + version_id: i32, + ) -> Result { + // 确定版本存在 + let _ = self.query_release_data_version_by_id(version_id).await?; + // 更新发布数据使用的版本 + let rd_table = ReleaseDataColumn::Table.name(); + let rd_used_version_id = ReleaseDataColumn::UsedVersionId.name(); + let rd_updated_at = ReleaseDataColumn::UpdatedAt.name(); + let rd_id = ReleaseDataColumn::Id.name(); + let rd_update_clause = format!( + "UPDATE {rd_table} SET {rd_used_version_id} = $1, {rd_updated_at} = 'now()' WHERE {rd_id} = $2 RETURNING *", + rd_table = rd_table, + rd_used_version_id = rd_used_version_id, + rd_id = rd_id + ); + let rd = sqlx::query_as(&rd_update_clause) + .bind(version_id) + .bind(release_id) + .fetch_one(&self.pool) + .await?; + Ok(rd) + } + + async fn create_draft_from_version( + &self, + version_id: i32, + user_id: i32, + ) -> Result { + // 查询版本数据 + let rdv = self.query_release_data_version_by_id(version_id).await?; + // 查询发布数据 + let rd = self.query_release_data_by_id(rdv.release_data_id).await?; + // 检查草稿名称是否已存在 + let name = format!("{}_{}", rd.name, chrono::Local::now().timestamp()); + // 创建草稿数据 + let draft = self + .create_draft_data( + CreateDraftData::new(&name, DataType::try_from(rd.data_type).unwrap(), user_id) + .with_data(&rdv.data) + .with_default_release_data_id(rd.id), + ) + .await?; + Ok(draft) + } +} + +#[cfg(test)] +mod tests { + use crate::{CreateDraftData, DraftDataAccessor, RtssDbAccessor}; + + use super::*; + use rtss_log::tracing::Level; + use sqlx::PgPool; + + #[test] + fn test_release_query() { + // 测试构造发布数据查询条件,名称过滤 + let expects = "WHERE name LIKE '%test%'"; + let query_with_name = ReleaseDataQuery::new().with_name("test".to_string()); + assert_eq!(query_with_name.build_filter(), expects); + + // 测试构造发布数据查询条件,用户id过滤 + let expects = "WHERE user_id = 1"; + let query_with_user_id = ReleaseDataQuery::new().with_user_id(1); + assert_eq!(query_with_user_id.build_filter(), expects); + + // 测试构造发布数据查询条件,数据类型过滤 + let expects = "WHERE data_type = 1"; + let query_with_data_type = ReleaseDataQuery::new().with_data_type(DataType::Em); + assert_eq!(query_with_data_type.build_filter(), expects); + + // 测试构造发布数据查询条件,是否上架过滤 + let expects = "WHERE is_published = true"; + let query_with_is_published = ReleaseDataQuery::new().with_is_published(true); + assert_eq!(query_with_is_published.build_filter(), expects); + + // 测试构造发布数据查询条件,多条件过滤 + let expects = + "WHERE name LIKE '%test%' AND user_id = 1 AND data_type = 1 AND is_published = true"; + let query_with_all = ReleaseDataQuery::new() + .with_name("test".to_string()) + .with_user_id(1) + .with_data_type(DataType::Em) + .with_is_published(true); + assert_eq!(query_with_all.build_filter(), expects); + } + + // You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here. + #[sqlx::test(migrator = "crate::MIGRATOR")] + async fn test_basic_use(pool: PgPool) -> Result<(), DbAccessError> { + rtss_log::Logging::default().with_level(Level::DEBUG).init(); + let accessor = RtssDbAccessor::new(pool); + // 创建草稿 + let data = "test".as_bytes(); + let draft = accessor + .create_draft_data( + CreateDraftData::new("test", rtss_dto::common::DataType::Em, 1).with_data(data), + ) + .await?; + let name = "test_release"; + let description = "test release"; + // 发布到默认发布数据 + let result = accessor.release_to_existing(draft.id, description).await; + assert!(result.is_err()); + if let Some(e) = result.err() { + match e { + DbAccessError::DataError(_) => { + println!("发布到已存在发布数据:草稿未设置默认发布数据测试正确"); + } + _ => panic!("发布到默认发布数据草稿未设置默认发布数据测试错误: {:?}", e), + } + } + + // 发布新版本 + let (release_data, version1) = accessor + .release_new_from_draft(draft.id, name, &description) + .await?; + assert_eq!(release_data.name, name); + // 检查使用版本是刚刚发布的版本 + assert_eq!(release_data.used_version_id, Some(version1.id)); + // 检查版本数据 + assert_eq!(version1.data, data); + // 检查版本描述 + assert_eq!(version1.description, description); + // 检查上架状态 + assert_eq!(release_data.is_published, true); + // 检查草稿数据的默认发布数据id + let draft = accessor.query_draft_data_by_id(draft.id).await?; + assert_eq!(draft.default_release_data_id, Some(release_data.id)); + println!("发布新版本测试成功"); + + // name重复检查 + let exist = accessor.is_release_data_name_exist(name).await?; + assert_eq!(exist, true); + + // 修改草稿数据 + let data = "test2".as_bytes(); + accessor.update_draft_data_data(draft.id, data).await?; + // 发布到已存在发布数据成功测试 + let (release_data, version2) = accessor.release_to_existing(draft.id, description).await?; + assert_eq!(release_data.name, name); + // 检查使用版本是刚刚发布的版本 + assert_eq!(release_data.used_version_id, Some(version2.id)); + // 数据检查 + assert_eq!(version2.data, data); + println!("发布到已存在发布数据测试成功"); + + // 测试更新发布数据名称 + let new_name = "test_release_new"; + let release_data = accessor + .update_release_data_name(release_data.id, new_name) + .await?; + assert_eq!(release_data.name, new_name); + assert!(release_data.updated_at > release_data.created_at); + println!("更新发布数据名称测试成功"); + + // 测试更新发布数据上架状态 + let release_data = accessor + .update_release_data_published(release_data.id, false) + .await?; + assert_eq!(release_data.is_published, false); + assert!(release_data.updated_at > release_data.created_at); + println!("更新发布数据上架状态测试成功"); + + // 设置使用的版本 + let release_data = accessor + .set_used_version(release_data.id, version1.id) + .await?; + assert_eq!(release_data.used_version_id, Some(version1.id)); + assert!(release_data.updated_at > release_data.created_at); + println!("设置使用的版本测试成功"); + + // 查询发布数据所有版本 + let versions = accessor + .query_release_data_versions(release_data.id) + .await?; + assert_eq!(versions.len(), 2); + println!("查询发布数据所有版本测试成功: {:?}", versions); + + // 查询发布数据详情 + let (release_data, used_version) = accessor + .query_release_data_with_used_version(release_data.id) + .await?; + assert_eq!(release_data.used_version_id.unwrap(), used_version.id); + println!("查询发布数据详情测试成功"); + + // 从版本创建草稿 + let draft = accessor + .create_draft_from_version(used_version.id, 1) + .await?; + assert_eq!(draft.data, Some(used_version.data)); + assert_eq!(draft.default_release_data_id, Some(release_data.id)); + println!("从版本创建草稿测试成功"); + + // 构造分页查询所需发布数据,4个人每人发布2个数据 + for i in 2..6 { + let name = format!("test_{}", i); + let description = format!("test release {}", i); + let data = "test data".as_bytes(); + let draft = accessor + .create_draft_data( + CreateDraftData::new(&name, rtss_dto::common::DataType::Em, i).with_data(data), + ) + .await?; + let (release_data, _) = accessor + .release_new_from_draft(draft.id, &name, &description) + .await?; + assert_eq!(release_data.name, name); + let another_name = format!("{}_another", name); + let (release_data, _) = accessor + .release_new_from_draft(draft.id, &another_name, &description) + .await?; + assert_eq!(release_data.name, another_name); + } + // 分页查询发布数据,无条件 + let query = ReleaseDataQuery::new(); + let page = PageQuery::new(1, 100); + let page_result = accessor.query_release_data_list(query, page).await?; + assert_eq!(page_result.total, 9); + // 分页查询发布数据,按是否上架过滤 + let query = ReleaseDataQuery::new().with_is_published(true); + let page = PageQuery::new(1, 100); + let page_result = accessor.query_release_data_list(query, page).await?; + assert_eq!(page_result.total, 8); + // 分页查询发布数据,按名称过滤 + let query = ReleaseDataQuery::new().with_name("test_2".to_string()); + let page = PageQuery::new(1, 10); + let page_result = accessor.query_release_data_list(query, page).await?; + assert_eq!(page_result.total, 2); + // 分页查询发布数据,按用户id过滤 + let query = ReleaseDataQuery::new().with_user_id(2); + let page = PageQuery::new(1, 10); + let page_result = accessor.query_release_data_list(query, page).await?; + assert_eq!(page_result.total, 2); + // 分页查询发布数据,按数据类型过滤 + let query = ReleaseDataQuery::new().with_data_type(rtss_dto::common::DataType::Em); + let page = PageQuery::new(1, 10); + let page_result = accessor.query_release_data_list(query, page).await?; + assert_eq!(page_result.total, 9); + println!("分页查询发布数据测试成功"); + + Ok(()) + } +} diff --git a/crates/rtss_db/src/error.rs b/crates/rtss_db/src/error.rs new file mode 100644 index 0000000..868e94e --- /dev/null +++ b/crates/rtss_db/src/error.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DbAccessError { + #[error("未知的数据库访问错误")] + Unknown, + #[error("sqlx 错误: {0}")] + SqlxError(#[from] sqlx::Error), + #[error("数据已存在")] + RowExist, + #[error("数据错误:{0}")] + DataError(String), +} diff --git a/crates/rtss_db/src/lib.rs b/crates/rtss_db/src/lib.rs index b93cf3f..ac873b9 100644 --- a/crates/rtss_db/src/lib.rs +++ b/crates/rtss_db/src/lib.rs @@ -1,14 +1,27 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +pub mod common; +mod db_access; +mod error; +pub mod model; +pub use db_access::*; +pub use error::*; + +use sqlx::pool::PoolOptions; +pub use sqlx::types::chrono::*; + +pub static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("../../migrations"); + +pub async fn run_migrations(url: &str) -> anyhow::Result<()> { + let pool = PoolOptions::::new() + .acquire_timeout(std::time::Duration::from_secs(5)) + .connect(url) + .await?; + MIGRATOR.run(&pool).await?; + Ok(()) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +pub mod prelude { + pub use crate::common::*; + pub use crate::db_access::*; + pub use crate::model::*; + pub use crate::DbAccessError; } diff --git a/crates/rtss_db/src/model.rs b/crates/rtss_db/src/model.rs new file mode 100644 index 0000000..378d4ec --- /dev/null +++ b/crates/rtss_db/src/model.rs @@ -0,0 +1,306 @@ +use sqlx::types::chrono::{DateTime, Local}; + +use crate::common::TableColumn; + +/// 数据库表 rtss.draft_data 列映射 +#[derive(Debug)] +pub(crate) enum DraftDataColumn { + Table, + Id, + Name, + DataType, + Data, + DefaultReleaseDataId, + UserId, + CreatedAt, + UpdatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct DraftDataModel { + pub id: i32, + pub name: String, + pub data_type: i32, + #[sqlx(default)] + pub data: Option>, + #[sqlx(default)] + pub default_release_data_id: Option, + pub user_id: i32, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// 数据库表 rtss.release_data 列映射 +#[derive(Debug)] +pub(crate) enum ReleaseDataColumn { + Table, + Id, + Name, + DataType, + UsedVersionId, + UserId, + IsPublished, + #[allow(dead_code)] + CreatedAt, + UpdatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct ReleaseDataModel { + pub id: i32, + pub name: String, + pub data_type: i32, + pub used_version_id: Option, + pub user_id: i32, + pub is_published: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// 数据库表 rtss.release_data_version 列映射 +#[derive(Debug)] +pub(crate) enum ReleaseDataVersionColumn { + Table, + Id, + ReleaseDataId, + Data, + Version, + Description, + UserId, + CreatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct ReleaseDataVersionModel { + pub id: i32, + pub release_data_id: i32, + #[sqlx(default)] + pub data: Vec, + pub version: i32, + pub description: String, + pub user_id: i32, + pub created_at: DateTime, +} + +/// 数据库表 rtss.feature 列映射 +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum FeatureColumn { + Table, + Id, + FeatureType, + Name, + Description, + IsPublished, + CreatorId, + UpdaterId, + CreatedAt, + UpdatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct FeatureModel { + pub id: i32, + pub feature_type: i32, + pub name: String, + pub description: String, + pub is_published: bool, + pub creator_id: i32, + pub updater_id: i32, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// 数据库表 rtss.feature_release_data 列映射 +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum FeatureReleaseDataColumn { + Table, + FeatureId, + ReleaseDataId, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct FeatureReleaseDataModel { + pub feature_id: i32, + pub release_data_id: i32, +} + +/// 数据库表 rtss.feature_group 列映射 +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum FeatureGroupColumn { + Table, + Id, + Name, + Description, + IsPublished, + CreatorId, + UpdaterId, + CreatedAt, + UpdatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct FeatureGroupModel { + pub id: i32, + pub name: String, + pub description: String, + pub is_published: bool, + pub creator_id: i32, + pub updater_id: i32, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// 数据库表 rtss.feature_group_feature 列映射 +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum FeatureGroupFeatureColumn { + Table, + FeatureGroupId, + FeatureId, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct FeatureGroupFeatureModel { + pub feature_group_id: i32, + pub feature_id: i32, +} + +/// 数据库表 rtss.feature_config 列映射 +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) enum FeatureConfigColumn { + Table, + Id, + UserId, + FeatureId, + Config, + CreatedAt, + UpdatedAt, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct FeatureConfigModel { + pub id: i32, + pub user_id: i32, + pub feature_id: i32, + pub config: Vec, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +impl TableColumn for DraftDataColumn { + fn name(&self) -> &str { + match self { + DraftDataColumn::Table => "rtss.draft_data", + DraftDataColumn::Id => "id", + DraftDataColumn::Name => "name", + DraftDataColumn::DataType => "data_type", + DraftDataColumn::Data => "data", + DraftDataColumn::DefaultReleaseDataId => "default_release_data_id", + DraftDataColumn::UserId => "user_id", + DraftDataColumn::CreatedAt => "created_at", + DraftDataColumn::UpdatedAt => "updated_at", + } + } +} + +impl TableColumn for ReleaseDataColumn { + fn name(&self) -> &str { + match self { + ReleaseDataColumn::Table => "rtss.release_data", + ReleaseDataColumn::Id => "id", + ReleaseDataColumn::Name => "name", + ReleaseDataColumn::DataType => "data_type", + ReleaseDataColumn::UsedVersionId => "used_version_id", + ReleaseDataColumn::UserId => "user_id", + ReleaseDataColumn::IsPublished => "is_published", + ReleaseDataColumn::CreatedAt => "created_at", + ReleaseDataColumn::UpdatedAt => "updated_at", + } + } +} + +impl TableColumn for ReleaseDataVersionColumn { + fn name(&self) -> &str { + match self { + ReleaseDataVersionColumn::Table => "rtss.release_data_version", + ReleaseDataVersionColumn::Id => "id", + ReleaseDataVersionColumn::ReleaseDataId => "release_data_id", + ReleaseDataVersionColumn::Data => "data", + ReleaseDataVersionColumn::Version => "version", + ReleaseDataVersionColumn::Description => "description", + ReleaseDataVersionColumn::UserId => "user_id", + ReleaseDataVersionColumn::CreatedAt => "created_at", + } + } +} + +impl TableColumn for FeatureColumn { + fn name(&self) -> &str { + match self { + FeatureColumn::Table => "rtss.feature", + FeatureColumn::Id => "id", + FeatureColumn::FeatureType => "feature_type", + FeatureColumn::Name => "name", + FeatureColumn::Description => "description", + FeatureColumn::IsPublished => "is_published", + FeatureColumn::CreatorId => "creator_id", + FeatureColumn::UpdaterId => "updater_id", + FeatureColumn::CreatedAt => "created_at", + FeatureColumn::UpdatedAt => "updated_at", + } + } +} + +impl TableColumn for FeatureReleaseDataColumn { + fn name(&self) -> &str { + match self { + FeatureReleaseDataColumn::Table => "rtss.feature_release_data", + FeatureReleaseDataColumn::FeatureId => "feature_id", + FeatureReleaseDataColumn::ReleaseDataId => "release_data_id", + } + } +} + +impl TableColumn for FeatureGroupColumn { + fn name(&self) -> &str { + match self { + FeatureGroupColumn::Table => "rtss.feature_group", + FeatureGroupColumn::Id => "id", + FeatureGroupColumn::Name => "name", + FeatureGroupColumn::Description => "description", + FeatureGroupColumn::IsPublished => "is_published", + FeatureGroupColumn::CreatorId => "creator_id", + FeatureGroupColumn::UpdaterId => "updater_id", + FeatureGroupColumn::CreatedAt => "created_at", + FeatureGroupColumn::UpdatedAt => "updated_at", + } + } +} + +impl TableColumn for FeatureGroupFeatureColumn { + fn name(&self) -> &str { + match self { + FeatureGroupFeatureColumn::Table => "rtss.feature_group_feature", + FeatureGroupFeatureColumn::FeatureGroupId => "feature_group_id", + FeatureGroupFeatureColumn::FeatureId => "feature_id", + } + } +} + +impl TableColumn for FeatureConfigColumn { + fn name(&self) -> &str { + match self { + FeatureConfigColumn::Table => "rtss.feature_config", + FeatureConfigColumn::Id => "id", + FeatureConfigColumn::UserId => "user_id", + FeatureConfigColumn::FeatureId => "feature_id", + FeatureConfigColumn::Config => "config", + FeatureConfigColumn::CreatedAt => "created_at", + FeatureConfigColumn::UpdatedAt => "updated_at", + } + } +} diff --git a/crates/rtss_dto/Cargo.toml b/crates/rtss_dto/Cargo.toml index 538bcab..982a90c 100644 --- a/crates/rtss_dto/Cargo.toml +++ b/crates/rtss_dto/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] prost = "0.13" +async-graphql = { version = "7.0.7", features = ["chrono"] } +sqlx = { workspace = true } [build-dependencies] prost-build = "0.13" diff --git a/crates/rtss_dto/build.rs b/crates/rtss_dto/build.rs index 5b42a38..15e100b 100644 --- a/crates/rtss_dto/build.rs +++ b/crates/rtss_dto/build.rs @@ -1,7 +1,8 @@ +use std::process::Command; + use prost_build::Config; fn main() { - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=../../rtss-proto-msg/src/basic_signal_data.proto"); + // println!("cargo:rerun-if-changed=build.rs"); #[cfg(target_os = "windows")] { std::env::set_var( @@ -11,9 +12,21 @@ fn main() { } Config::new() .out_dir("src/pb") + .type_attribute("common.DataType", "#[derive(sqlx::Type)]") + .type_attribute("common.DataType", "#[derive(async_graphql::Enum)]") + .type_attribute("common.FeatureType", "#[derive(sqlx::Type)]") .compile_protos( - &["../../rtss-proto-msg/src/basic_signal_data.proto"], + &[ + "../../rtss-proto-msg/src/em_data.proto", + "../../rtss-proto-msg/src/common.proto", + ], &["../../rtss-proto-msg/src/"], ) .unwrap(); + + // Run cargo fmt to format the generated code + Command::new("cargo") + .args(["fmt"]) + .status() + .expect("Failed to run cargo fmt on rtss-dto"); } diff --git a/crates/rtss_dto/src/lib.rs b/crates/rtss_dto/src/lib.rs index 9933e3f..cf2b39c 100644 --- a/crates/rtss_dto/src/lib.rs +++ b/crates/rtss_dto/src/lib.rs @@ -14,9 +14,9 @@ mod tests { #[test] fn test_encode_decode() { - let point = basic_signal_data::Point { x: 1.0, y: 2.0 }; + let point = common::Point { x: 1.0, y: 2.0 }; let encoded = point.encode_to_vec(); - let decoded = basic_signal_data::Point::decode(encoded.as_ref()).unwrap(); + let decoded = common::Point::decode(encoded.as_ref()).unwrap(); assert_eq!(point, decoded); println!( "point: {:?}, encoded: {:?}, decoded: {:?}", diff --git a/crates/rtss_dto/src/pb/basic_signal_data.rs b/crates/rtss_dto/src/pb/common.rs similarity index 50% rename from crates/rtss_dto/src/pb/basic_signal_data.rs rename to crates/rtss_dto/src/pb/common.rs index e99a88b..801aad7 100644 --- a/crates/rtss_dto/src/pb/basic_signal_data.rs +++ b/crates/rtss_dto/src/pb/common.rs @@ -1,12 +1,4 @@ // This file is @generated by prost-build. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Storage { - #[prost(message, optional, tag = "1")] - pub canvas: ::core::option::Option, - #[prost(message, repeated, tag = "2")] - pub stations: ::prost::alloc::vec::Vec, -} /// 画布数据 #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -96,42 +88,91 @@ pub struct CommonInfo { #[prost(message, repeated, tag = "4")] pub children_transform: ::prost::alloc::vec::Vec, } -/// 公里标 -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct KilometerMark { - /// 公里标坐标系 - #[prost(string, tag = "1")] - pub coordinate: ::prost::alloc::string::String, - /// 公里标数值 - #[prost(int64, tag = "2")] - pub value: i64, +/// 数据类型 +#[derive( + sqlx::Type, + async_graphql::Enum, + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration, +)] +#[repr(i32)] +pub enum DataType { + /// 数据类型未知 + Unknown = 0, + /// 电子地图数据 + Em = 1, + /// IBP数据 + Ibp = 2, + /// PSL数据 + Psl = 3, + /// ISCS数据 + Iscs = 4, } -/// 车站数据 -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Station { - #[prost(message, optional, tag = "1")] - pub common: ::core::option::Option, - /// 车站名 - #[prost(string, tag = "2")] - pub name: ::prost::alloc::string::String, - /// 车站站名 - #[prost(string, tag = "3")] - pub zhan_name: ::prost::alloc::string::String, - /// 车站名拼音简写 - #[prost(string, tag = "4")] - pub name_pinyin: ::prost::alloc::string::String, - /// 公里标 - #[prost(message, optional, tag = "6")] - pub km: ::core::option::Option, - /// 是否集中站 - #[prost(bool, tag = "10")] - pub concentration: bool, - /// 是否车辆段 - #[prost(bool, tag = "11")] - pub depots: bool, - /// 集中站管理的车站-id - #[prost(uint32, repeated, tag = "13")] - pub manage_station_ids: ::prost::alloc::vec::Vec, +impl DataType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + DataType::Unknown => "DataType_Unknown", + DataType::Em => "DataType_Em", + DataType::Ibp => "DataType_Ibp", + DataType::Psl => "DataType_Psl", + DataType::Iscs => "DataType_Iscs", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DataType_Unknown" => Some(Self::Unknown), + "DataType_Em" => Some(Self::Em), + "DataType_Ibp" => Some(Self::Ibp), + "DataType_Psl" => Some(Self::Psl), + "DataType_Iscs" => Some(Self::Iscs), + _ => None, + } + } +} +/// 功能特性类型 +#[derive( + sqlx::Type, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration, +)] +#[repr(i32)] +pub enum FeatureType { + /// 未知 + Unknown = 0, + /// 仿真 + Simulation = 1, + /// 运行图编制 + RunPlan = 2, +} +impl FeatureType { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + FeatureType::Unknown => "FeatureType_Unknown", + FeatureType::Simulation => "FeatureType_Simulation", + FeatureType::RunPlan => "FeatureType_RunPlan", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "FeatureType_Unknown" => Some(Self::Unknown), + "FeatureType_Simulation" => Some(Self::Simulation), + "FeatureType_RunPlan" => Some(Self::RunPlan), + _ => None, + } + } } diff --git a/crates/rtss_dto/src/pb/em_data.rs b/crates/rtss_dto/src/pb/em_data.rs new file mode 100644 index 0000000..151b5aa --- /dev/null +++ b/crates/rtss_dto/src/pb/em_data.rs @@ -0,0 +1,49 @@ +// This file is @generated by prost-build. +/// 电子地图数据 +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Em { + #[prost(message, optional, tag = "1")] + pub canvas: ::core::option::Option, + #[prost(message, repeated, tag = "2")] + pub stations: ::prost::alloc::vec::Vec, +} +/// 公里标 +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct KilometerMark { + /// 公里标坐标系 + #[prost(string, tag = "1")] + pub coordinate: ::prost::alloc::string::String, + /// 公里标数值 + #[prost(int64, tag = "2")] + pub value: i64, +} +/// 车站数据 +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Station { + #[prost(message, optional, tag = "1")] + pub common: ::core::option::Option, + /// 车站名 + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + /// 车站站名 + #[prost(string, tag = "3")] + pub zhan_name: ::prost::alloc::string::String, + /// 车站名拼音简写 + #[prost(string, tag = "4")] + pub name_pinyin: ::prost::alloc::string::String, + /// 公里标 + #[prost(message, optional, tag = "6")] + pub km: ::core::option::Option, + /// 是否集中站 + #[prost(bool, tag = "10")] + pub concentration: bool, + /// 是否车辆段 + #[prost(bool, tag = "11")] + pub depots: bool, + /// 集中站管理的车站-id + #[prost(uint32, repeated, tag = "13")] + pub manage_station_ids: ::prost::alloc::vec::Vec, +} diff --git a/crates/rtss_dto/src/pb/mod.rs b/crates/rtss_dto/src/pb/mod.rs index deb7093..7d7e7cf 100644 --- a/crates/rtss_dto/src/pb/mod.rs +++ b/crates/rtss_dto/src/pb/mod.rs @@ -1 +1,2 @@ -pub mod basic_signal_data; +pub mod common; +pub mod em_data; diff --git a/crates/rtss_log/src/lib.rs b/crates/rtss_log/src/lib.rs index ecdb4a1..8d81b04 100644 --- a/crates/rtss_log/src/lib.rs +++ b/crates/rtss_log/src/lib.rs @@ -20,13 +20,19 @@ pub struct Logging { impl Default for Logging { fn default() -> Self { Self { - filter: "wgpu=error,naga=warn".to_string(), + // 过滤器: like "naga=warn,axum=info"等 + filter: "".to_string(), level: Level::INFO, } } } impl Logging { + pub fn with_level(mut self, level: Level) -> Self { + self.level = level; + self + } + pub fn init(&self) { let finished_subscriber; let subscriber = Registry::default(); diff --git a/migrations/20240830095636_init.down.sql b/migrations/20240830095636_init.down.sql new file mode 100644 index 0000000..852f357 --- /dev/null +++ b/migrations/20240830095636_init.down.sql @@ -0,0 +1 @@ +DROP SCHEMA rtss CASCADE; diff --git a/migrations/20240830095636_init.up.sql b/migrations/20240830095636_init.up.sql new file mode 100644 index 0000000..9cc145e --- /dev/null +++ b/migrations/20240830095636_init.up.sql @@ -0,0 +1,234 @@ +-- 初始化数据库SCHEMA(所有轨道交通信号系统仿真的表、类型等都在rtss SCHEMA下) +CREATE SCHEMA rtss; + +-- 创建草稿数据表 +CREATE TABLE + rtss.draft_data ( + id SERIAL PRIMARY KEY, -- id 自增主键 + name VARCHAR(128) NOT NULL, -- 草稿名称 + data_type INT NOT NULL, -- 数据类型 + data BYTEA, -- 草稿数据 + default_release_data_id INT NULL, -- 默认发布数据id + user_id INT NOT NULL, -- 创建用户id + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 更新时间 + UNIQUE (name, user_id) -- 一个用户的草稿名称唯一 + ); + +-- 创建草稿数据用户索引 +CREATE INDEX ON rtss.draft_data (user_id); + +-- 创建草稿数据类型索引 +CREATE INDEX ON rtss.draft_data (data_type); + +-- 注释草稿数据表 +COMMENT ON TABLE rtss.draft_data IS '草稿数据表'; + +-- 注释草稿数据表字段 +COMMENT ON COLUMN rtss.draft_data.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.draft_data.name IS '草稿名称'; + +COMMENT ON COLUMN rtss.draft_data.data_type IS '数据类型'; + +COMMENT ON COLUMN rtss.draft_data.data IS '草稿数据'; + +COMMENT ON COLUMN rtss.draft_data.user_id IS '创建用户id'; + +COMMENT ON COLUMN rtss.draft_data.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.draft_data.updated_at IS '更新时间'; + +-- 创建发布数据表 +CREATE TABLE + rtss.release_data ( + id SERIAL PRIMARY KEY, -- id 自增主键 + name VARCHAR(128) NOT NULL UNIQUE, -- 发布数据名称(数据唯一标识) + data_type INT NOT NULL, -- 数据类型 + used_version_id INT NULL, -- 使用的版本数据id + user_id INT NOT NULL, -- 发布/更新用户id + is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架 + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()' -- 更新时间 + ); + +-- 注释发布数据表 +COMMENT ON TABLE rtss.release_data IS '发布数据表'; + +-- 注释发布数据表字段 +COMMENT ON COLUMN rtss.release_data.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.release_data.name IS '发布数据名称(数据唯一标识)'; + +COMMENT ON COLUMN rtss.release_data.data_type IS '数据类型'; + +COMMENT ON COLUMN rtss.release_data.used_version_id IS '使用的版本数据id'; + +COMMENT ON COLUMN rtss.release_data.user_id IS '发布/更新用户id'; + +COMMENT ON COLUMN rtss.release_data.is_published IS '是否上架'; + +COMMENT ON COLUMN rtss.release_data.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.release_data.updated_at IS '更新时间'; + +-- 创建发布数据版本表 +CREATE TABLE + rtss.release_data_version ( + id SERIAL PRIMARY KEY, -- id 自增主键 + release_data_id INT NOT NULL, -- 发布数据id + data BYTEA NOT NULL, -- 数据 + version SERIAL NOT NULL, -- 版本号 + description TEXT NOT NULL, -- 版本描述 + user_id INT NOT NULL, -- 发布用户id + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + FOREIGN KEY (release_data_id) REFERENCES rtss.release_data (id) ON DELETE CASCADE + ); + +-- 创建发布数据当前版本外键 +ALTER TABLE rtss.release_data ADD FOREIGN KEY (used_version_id) REFERENCES rtss.release_data_version (id) ON DELETE SET NULL; + +-- 创建草稿数据默认发布数据外键 +ALTER TABLE rtss.draft_data ADD FOREIGN KEY (default_release_data_id) REFERENCES rtss.release_data (id) ON DELETE SET NULL; + +-- 注释发布数据版本表 +COMMENT ON TABLE rtss.release_data_version IS '发布数据版本表'; + +-- 注释发布数据版本表字段 +COMMENT ON COLUMN rtss.release_data_version.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.release_data_version.release_data_id IS '发布数据id'; + +COMMENT ON COLUMN rtss.release_data_version.data IS '数据'; + +COMMENT ON COLUMN rtss.release_data_version.version IS '版本号'; + +COMMENT ON COLUMN rtss.release_data_version.description IS '版本描述'; + +COMMENT ON COLUMN rtss.release_data_version.user_id IS '发布用户id'; + +COMMENT ON COLUMN rtss.release_data_version.created_at IS '创建时间'; + +-- 创建feature表 +CREATE TABLE + rtss.feature ( + id SERIAL PRIMARY KEY, -- id 自增主键 + feature_type INT NOT NULL, -- feature类型 + name VARCHAR(128) NOT NULL UNIQUE, -- feature名称 + description TEXT NOT NULL, -- feature描述 + is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架 + creator_id INT NOT NULL, -- 创建用户id + updater_id INT NOT NULL, -- 更新用户id + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()' -- 更新时间 + ); + +-- 注释仿真feature表 +COMMENT ON TABLE rtss.feature IS 'feature表'; + +-- 注释仿真feature表字段 +COMMENT ON COLUMN rtss.feature.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.feature.feature_type IS 'feature类型'; + +COMMENT ON COLUMN rtss.feature.name IS 'feature名称'; + +COMMENT ON COLUMN rtss.feature.description IS 'feature描述'; + +COMMENT ON COLUMN rtss.feature.is_published IS '是否上架'; + +COMMENT ON COLUMN rtss.feature.creator_id IS '创建用户id'; + +COMMENT ON COLUMN rtss.feature.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.feature.updated_at IS '更新时间'; + +-- 创建仿真feature和发布数据关联表 +CREATE TABLE + rtss.feature_release_data ( + feature_id INT NOT NULL, -- 仿真feature id + release_data_id INT NOT NULL, -- 发布数据id + PRIMARY KEY (feature_id, release_data_id), + FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE, + FOREIGN KEY (release_data_id) REFERENCES rtss.release_data (id) ON DELETE CASCADE + ); + +-- 注释仿真feature和发布数据关联表 +COMMENT ON TABLE rtss.feature_release_data IS '仿真feature和发布数据关联表'; + +-- 注释仿真feature和发布数据关联表字段 +COMMENT ON COLUMN rtss.feature_release_data.feature_id IS '仿真feature id'; + +COMMENT ON COLUMN rtss.feature_release_data.release_data_id IS '发布数据id'; + +-- 创建feature group表 +CREATE TABLE + rtss.feature_group ( + id SERIAL PRIMARY KEY, -- id 自增主键 + name VARCHAR(128) NOT NULL UNIQUE, -- feature group名称 + description TEXT NOT NULL, -- feature group描述 + is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架 + creator_id INT NOT NULL, -- 创建用户id + updater_id INT NOT NULL, -- 更新用户id + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()' -- 更新时间 + ); + +-- 注释仿真feature group表 +COMMENT ON TABLE rtss.feature_group IS 'feature group表'; + +-- 注释仿真feature group表字段 +COMMENT ON COLUMN rtss.feature_group.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.feature_group.name IS 'feature group名称'; + +COMMENT ON COLUMN rtss.feature_group.description IS 'feature group描述'; + +COMMENT ON COLUMN rtss.feature_group.is_published IS '是否上架'; + +COMMENT ON COLUMN rtss.feature_group.creator_id IS '创建用户id'; + +COMMENT ON COLUMN rtss.feature_group.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.feature_group.updated_at IS '更新时间'; + +-- 创建feature group和feature关联表 +CREATE TABLE + rtss.feature_group_feature ( + feature_group_id INT NOT NULL, -- feature group id + feature_id INT NOT NULL, -- feature id + PRIMARY KEY (feature_id, feature_group_id), + FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE, + FOREIGN KEY (feature_group_id) REFERENCES rtss.feature_group (id) ON DELETE CASCADE + ); + +-- 注释仿真feature group和feature关联表 +COMMENT ON TABLE rtss.feature_group_feature IS '仿真feature group和feature关联表'; + +-- 创建用户feature配置表 +CREATE TABLE + rtss.feature_config ( + id SERIAL PRIMARY KEY, -- id 自增主键 + user_id INT NOT NULL, -- 用户id + feature_id INT NOT NULL, -- 仿真feature id + config BYTEA NOT NULL, -- 配置 + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 创建时间 + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()', -- 更新时间 + FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE + ); + +-- 注释用户feature配置表 +COMMENT ON TABLE rtss.feature_config IS '用户feature配置表'; + +-- 注释用户feature配置表字段 +COMMENT ON COLUMN rtss.feature_config.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.feature_config.user_id IS '用户id'; + +COMMENT ON COLUMN rtss.feature_config.feature_id IS '仿真feature id'; + +COMMENT ON COLUMN rtss.feature_config.config IS '配置'; + +COMMENT ON COLUMN rtss.feature_config.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.feature_config.updated_at IS '更新时间'; diff --git a/rtss-proto-msg b/rtss-proto-msg index 9dee989..4e447be 160000 --- a/rtss-proto-msg +++ b/rtss-proto-msg @@ -1 +1 @@ -Subproject commit 9dee9892e87b1dca12a1753077e64aca7fced4b8 +Subproject commit 4e447beffa31c94b30bcee57a7cf229dcf01351f diff --git a/src/app_config.rs b/src/app_config.rs new file mode 100644 index 0000000..a0ab76c --- /dev/null +++ b/src/app_config.rs @@ -0,0 +1,84 @@ +use std::env; + +use config::{Config, ConfigError, Environment, File}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Server { + pub port: u16, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Database { + pub url: String, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Log { + level: String, +} + +impl From for rtss_log::Logging { + fn from(log: Log) -> Self { + rtss_log::Logging { + level: log.level.parse().unwrap(), + ..Default::default() + } + } +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct AppConfig { + pub server: Server, + pub log: Log, + pub database: Database, +} + +impl AppConfig { + pub fn new(dir: &str) -> Result { + let run_mode = env::var("RUN_MODE") + .unwrap_or_else(|_| "dev".into()) + .trim() + .to_string(); + println!("RUN_MODE: {}", run_mode); + + // log 当前目录 + // println!("Current dir: {:?}", std::env::current_dir().unwrap()); + + let s = Config::builder() + // Start off by merging in the "default" configuration file + .add_source(File::with_name(&format!("{dir}/default"))) + // Add in a local configuration file + // This file shouldn't be checked in to git + .add_source(File::with_name(&format!("{dir}/local")).required(false)) + // Add in the current environment file + // Default to 'dev' env + // Note that this file is _optional_ + .add_source(File::with_name(&format!("{dir}/{run_mode}")).required(true)) + // Add in settings from the environment (with a prefix of RTSS_SIM) + // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key + .add_source(Environment::with_prefix("RTSS_SIM").separator("_")) + // You may also programmatically change settings + // .set_override("database.url", "postgres://")? + // build the configuration + .build()?; + + // You can deserialize (and thus freeze) the entire configuration as + s.try_deserialize() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_app_config() { + std::env::set_var("RUN_MODE", "local_test"); + let config = AppConfig::new("conf").unwrap(); + println!("{:?}", config); + } +} diff --git a/src/cmd.rs b/src/cmd.rs new file mode 100644 index 0000000..83f269c --- /dev/null +++ b/src/cmd.rs @@ -0,0 +1,41 @@ +use clap::Parser; +use enum_dispatch::enum_dispatch; + +use crate::{app_config, db::DbSubCommand, CmdExecutor}; + +#[derive(Parser, Debug)] +#[command(name = "rtss-sim", version, author, about, long_about = None)] +pub struct Cmd { + #[command(subcommand)] + pub cmd: SubCommand, +} + +#[derive(Parser, Debug)] +#[enum_dispatch(CmdExecutor)] +pub enum SubCommand { + #[command(name = "serve")] + Serve(ServerOpts), + #[command(name = "db", subcommand, about = "Database operations")] + Db(DbSubCommand), +} + +#[derive(Parser, Debug)] +pub struct ServerOpts { + #[clap(short, long, required = false, default_value = "conf")] + config_path: String, +} + +impl CmdExecutor for ServerOpts { + async fn execute(&self) -> anyhow::Result<()> { + println!("ServerOpts: {:?}", self); + let app_config = + app_config::AppConfig::new(&self.config_path).expect("Failed to load app config"); + let log: rtss_log::Logging = app_config.log.into(); + log.init(); + rtss_api::serve(rtss_api::ServerConfig::new( + &app_config.database.url, + app_config.server.port, + )) + .await + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..9cfa67c --- /dev/null +++ b/src/db.rs @@ -0,0 +1,28 @@ +use clap::Parser; +use enum_dispatch::enum_dispatch; + +use crate::{app_config, CmdExecutor}; + +#[derive(Parser, Debug)] +#[enum_dispatch(CmdExecutor)] +pub enum DbSubCommand { + #[command(name = "migrate", about = "Migrate database")] + Migrate(MigrateOpts), +} + +#[derive(Parser, Debug)] +pub struct MigrateOpts { + #[clap(long, required = false, default_value = "conf")] + config_path: String, + #[clap(long, required = false, default_value = "migrations")] + file_path: String, +} + +impl CmdExecutor for MigrateOpts { + async fn execute(&self) -> anyhow::Result<()> { + let app_config = + app_config::AppConfig::new(&self.config_path).expect("Failed to load app config"); + println!("{:?}", app_config); + rtss_db::run_migrations(&app_config.database.url).await + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b5659b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,13 @@ +mod app_config; +mod cmd; +mod db; + +pub use cmd::*; +use db::*; +use enum_dispatch::enum_dispatch; + +#[allow(async_fn_in_trait)] +#[enum_dispatch] +pub trait CmdExecutor { + async fn execute(&self) -> anyhow::Result<()>; +} diff --git a/src/main.rs b/src/main.rs index d483313..8931280 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,9 @@ -use rtss_api::ServerConfig; +use clap::Parser; +use rtss_simulation::{Cmd, CmdExecutor}; #[tokio::main] -async fn main() { - rtss_log::Logging { - level: rtss_log::tracing::Level::DEBUG, - ..Default::default() - } - .init(); - rtss_api::serve(ServerConfig::default()).await; +async fn main() -> anyhow::Result<()> { + let cmd = Cmd::parse(); + cmd.cmd.execute().await?; + Ok(()) }