This commit is contained in:
2026-05-10 03:16:13 +02:00
parent 5f05108132
commit 0b441b0759
77 changed files with 2100 additions and 71 deletions
Generated
+329 -43
View File
@@ -128,6 +128,18 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "argon2"
version = "0.6.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5"
dependencies = [
"base64ct",
"blake2",
"cpufeatures 0.3.0",
"password-hash",
]
[[package]] [[package]]
name = "arraydeque" name = "arraydeque"
version = "0.5.1" version = "0.5.1"
@@ -478,6 +490,58 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"serde_core",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@@ -525,6 +589,15 @@ dependencies = [
"wyz", "wyz",
] ]
[[package]]
name = "blake2"
version = "0.11.0-rc.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690"
dependencies = [
"digest 0.11.3",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -534,6 +607,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block-buffer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
dependencies = [
"hybrid-array",
]
[[package]] [[package]]
name = "blocking" name = "blocking"
version = "1.6.2" version = "1.6.2"
@@ -619,9 +701,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.60" version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@@ -729,6 +811,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cmov"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.5" version = "1.0.5"
@@ -834,9 +922,9 @@ dependencies = [
[[package]] [[package]]
name = "crc-catalog" name = "crc-catalog"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
[[package]] [[package]]
name = "criterion" name = "criterion"
@@ -924,6 +1012,24 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-common"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
]
[[package]]
name = "ctutils"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
dependencies = [
"cmov",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.11" version = "0.20.11"
@@ -1007,12 +1113,23 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer 0.10.4",
"const-oid", "const-oid",
"crypto-common", "crypto-common 0.1.7",
"subtle", "subtle",
] ]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer 0.12.0",
"crypto-common 0.2.1",
"ctutils",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@@ -1430,7 +1547,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -1442,6 +1559,95 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hybrid-array"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5"
dependencies = [
"typenum",
]
[[package]]
name = "hyper"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"bytes",
"http",
"http-body",
"hyper",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.65" version = "0.1.65"
@@ -1573,9 +1779,9 @@ dependencies = [
[[package]] [[package]]
name = "idna_adapter" name = "idna_adapter"
version = "1.2.1" version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [ dependencies = [
"icu_normalizer", "icu_normalizer",
"icu_properties", "icu_properties",
@@ -1645,9 +1851,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.95" version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -1749,9 +1955,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.185" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libm" name = "libm"
@@ -1768,7 +1974,7 @@ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.4", "redox_syscall 0.7.5",
] ]
[[package]] [[package]]
@@ -1832,6 +2038,12 @@ dependencies = [
"regex-automata", "regex-automata",
] ]
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -1839,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -1865,6 +2077,12 @@ dependencies = [
"sea-orm-migration", "sea-orm-migration",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.2.0" version = "1.2.0"
@@ -2034,9 +2252,11 @@ dependencies = [
name = "oxspeak_server" name = "oxspeak_server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argon2",
"axum",
"bitflags",
"config", "config",
"event_bus", "event_bus",
"glob",
"log", "log",
"migration", "migration",
"parking_lot", "parking_lot",
@@ -2091,6 +2311,16 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "password-hash"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
dependencies = [
"getrandom 0.4.2",
"phc",
]
[[package]] [[package]]
name = "pathdiff" name = "pathdiff"
version = "0.2.3" version = "0.2.3"
@@ -2164,6 +2394,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "phc"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
dependencies = [
"base64ct",
"ctutils",
"getrandom 0.4.2",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.17" version = "0.2.17"
@@ -2484,9 +2725,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@@ -2579,7 +2820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"digest", "digest 0.10.7",
"num-bigint-dig", "num-bigint-dig",
"num-integer", "num-integer",
"num-traits", "num-traits",
@@ -2604,9 +2845,9 @@ dependencies = [
[[package]] [[package]]
name = "rust_decimal" name = "rust_decimal"
version = "1.41.0" version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"borsh", "borsh",
@@ -2907,6 +3148,17 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
"serde_core",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.1.1" version = "1.1.1"
@@ -2936,7 +3188,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures 0.2.17",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -2947,7 +3199,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures 0.2.17",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -2981,7 +3233,7 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
@@ -3137,7 +3389,7 @@ dependencies = [
"bytes", "bytes",
"chrono", "chrono",
"crc", "crc",
"digest", "digest 0.10.7",
"dotenvy", "dotenvy",
"either", "either",
"futures-channel", "futures-channel",
@@ -3301,6 +3553,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.2"
@@ -3424,9 +3682,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.52.2" version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -3512,6 +3770,34 @@ version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.44" version = "0.1.44"
@@ -3658,9 +3944,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "utoipa" name = "utoipa"
version = "5.4.0" version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@@ -3670,9 +3956,9 @@ dependencies = [
[[package]] [[package]]
name = "utoipa-gen" name = "utoipa-gen"
version = "5.4.0" version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3759,9 +4045,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.118" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -3773,9 +4059,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.68" version = "0.4.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -3783,9 +4069,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.118" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -3793,9 +4079,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.118" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@@ -3806,9 +4092,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.118" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -3849,9 +4135,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.95" version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
+4 -2
View File
@@ -11,7 +11,8 @@ crate-type = ["rlib"]
members = [".", "migration", "event_bus"] members = [".", "migration", "event_bus"]
[dependencies] [dependencies]
tokio = { version = "1.52.2", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
axum = "0.8"
config = "0.15.22" config = "0.15.22"
sea-orm = { version = "2.0.0-rc.38", features = ["sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio", "with-chrono", "with-uuid", "with-json", "schema-sync"] } sea-orm = { version = "2.0.0-rc.38", features = ["sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "runtime-tokio", "with-chrono", "with-uuid", "with-json", "schema-sync"] }
migration = { path = "migration" } migration = { path = "migration" }
@@ -26,4 +27,5 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "time"]
thiserror = "2" thiserror = "2"
utoipa = { version = "5", features = ["uuid"] } utoipa = { version = "5", features = ["uuid"] }
log = "0.4" log = "0.4"
glob = "0.3" bitflags = "2.11.1"
argon2 = { version = "0.6.0-rc.8", features = ["password-hash"] }
+45 -14
View File
@@ -28,7 +28,19 @@ impl MigrationTrait for Migration {
.default(Expr::current_timestamp()), .default(Expr::current_timestamp()),
) )
.col( .col(
ColumnDef::new(Alias::new("default_permissions")) ColumnDef::new(Alias::new("default_server_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("default_channel_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("default_voice_permissions"))
.big_integer() .big_integer()
.not_null() .not_null()
.default(0), .default(0),
@@ -120,11 +132,6 @@ impl MigrationTrait for Migration {
.not_null() .not_null()
.default(Expr::current_timestamp()), .default(Expr::current_timestamp()),
) )
.col(
ColumnDef::new(Alias::new("default_permissions"))
.big_integer()
.null(),
)
// Indexes créés après // Indexes créés après
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
@@ -317,7 +324,19 @@ impl MigrationTrait for Migration {
.default(false), .default(false),
) )
.col( .col(
ColumnDef::new(Alias::new("permissions")) ColumnDef::new(Alias::new("server_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("channel_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("voice_permissions"))
.big_integer() .big_integer()
.not_null() .not_null()
.default(0), .default(0),
@@ -361,12 +380,6 @@ impl MigrationTrait for Migration {
.not_null() .not_null()
.default("member".to_owned()), .default("member".to_owned()),
) )
.col(
ColumnDef::new(Alias::new("permissions"))
.big_integer()
.not_null()
.default(0),
)
.col( .col(
ColumnDef::new(Alias::new("joined_at")) ColumnDef::new(Alias::new("joined_at"))
.timestamp_with_time_zone() .timestamp_with_time_zone()
@@ -553,7 +566,19 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new("server_id").uuid().not_null()) .col(ColumnDef::new("server_id").uuid().not_null())
.col(ColumnDef::new("name").string().not_null()) .col(ColumnDef::new("name").string().not_null())
.col( .col(
ColumnDef::new("permissions") ColumnDef::new(Alias::new("server_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("channel_permissions"))
.big_integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Alias::new("voice_permissions"))
.big_integer() .big_integer()
.not_null() .not_null()
.default(0), .default(0),
@@ -570,6 +595,12 @@ impl MigrationTrait for Migration {
.not_null() .not_null()
.default(Expr::current_timestamp()), .default(Expr::current_timestamp()),
) )
.col(
ColumnDef::new("edited_at")
.timestamp_with_time_zone()
.null()
.default(Expr::current_timestamp()),
)
.foreign_key( .foreign_key(
ForeignKey::create() ForeignKey::create()
.name("fk_group_server") .name("fk_group_server")
+33
View File
@@ -0,0 +1,33 @@
pub mod state;
use crate::config::AppConfig;
use crate::database::Database;
use migration::{Migrator, MigratorTrait};
pub use state::AppState;
use std::sync::Arc;
pub struct App {
pub state: AppState,
}
impl App {
pub async fn build(config: AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
let db_manager = Database::init(&config.database.url).await?;
let db = db_manager.get_connection().clone();
Migrator::up(&db, None).await?;
let state = AppState {
db,
config: Arc::new(config),
};
Ok(Self { state })
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("Starting services...");
tokio::select! {}
}
}
+9
View File
@@ -0,0 +1,9 @@
use crate::config::AppConfig;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct AppState {
pub db: DatabaseConnection,
pub config: Arc<AppConfig>,
}
+3 -7
View File
@@ -1,6 +1,6 @@
use std::time::Duration;
use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection, DbErr}; use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection, DbErr};
use migration::{Migrator, MigratorTrait}; use std::time::Duration;
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {
@@ -19,10 +19,6 @@ impl Database {
let connection = SeaDatabase::connect(opt).await?; let connection = SeaDatabase::connect(opt).await?;
// On lance les migrations ici.
// Si ça échoue, le programme s'arrête proprement à l'init.
Migrator::up(&connection, None).await?;
Ok(Self { connection }) Ok(Self { connection })
} }
@@ -31,4 +27,4 @@ impl Database {
pub fn get_connection(&self) -> &DatabaseConnection { pub fn get_connection(&self) -> &DatabaseConnection {
&self.connection &self.connection
} }
} }
+1
View File
@@ -0,0 +1 @@
pub mod server;
+23
View File
@@ -0,0 +1,23 @@
use std::net::SocketAddr;
use axum::Router;
use tokio::net::TcpListener;
use crate::config::AppConfig;
use crate::routes;
pub async fn start(config: &AppConfig) -> Result<(), Box<dyn std::error::Error>> {
let addr = SocketAddr::new(
std::net::IpAddr::V4(config.network.host),
config.network.tcp_port,
);
let app: Router = routes::router();
let listener = TcpListener::bind(addr).await?;
tracing::info!(%addr, "HTTP server listening");
axum::serve(listener, app).await?;
Ok(())
}
+7
View File
@@ -1,4 +1,11 @@
pub mod config; pub mod config;
pub mod database; pub mod database;
pub mod http;
pub mod models; pub mod models;
// pub mod permissions_old;
pub mod core;
pub mod permissions;
pub mod repositories;
pub mod routes;
pub mod udp; pub mod udp;
pub mod utils;
+9 -2
View File
@@ -1,4 +1,7 @@
use migration::{Migrator, MigratorTrait};
use oxspeak_server_lib::config::AppConfig; use oxspeak_server_lib::config::AppConfig;
use oxspeak_server_lib::core::App;
use oxspeak_server_lib::database::Database;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -19,10 +22,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
AppConfig::gen_config()?; AppConfig::gen_config()?;
tracing::info!(config_path = "config.toml", "Generated"); tracing::info!(config_path = "config.toml", "Generated");
tracing::info!("Config generated, please edit config.toml"); tracing::info!("Config generated, please edit config.toml");
return Ok(()) return Ok(());
} }
let config = AppConfig::load()?; let config = AppConfig::load()?;
tracing::info!(?config, "Loaded config"); tracing::info!(?config, "Loaded config");
// 3. Build & Run
let app = App::build(config).await?;
app.run().await;
Ok(()) Ok(())
} }
+5 -1
View File
@@ -11,9 +11,13 @@ pub struct Model {
pub id: Uuid, pub id: Uuid,
pub server_id: Uuid, pub server_id: Uuid,
pub name: String, pub name: String,
pub permissions: u64,
pub is_default: bool, pub is_default: bool,
pub created_at: DateTimeUtc, pub created_at: DateTimeUtc,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub server_permissions: i64,
pub channel_permissions: i64,
pub voice_permissions: i64,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+20 -1
View File
@@ -1,5 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
use crate::permissions::{ChannelPermission, PermissionSet, ServerPermission, VoicePermission};
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use sea_orm::prelude::async_trait::async_trait; use sea_orm::prelude::async_trait::async_trait;
use sea_orm::Set; use sea_orm::Set;
@@ -13,7 +14,11 @@ pub struct Model {
pub password: Option<String>, pub password: Option<String>,
pub created_at: DateTimeUtc, pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc, pub updated_at: DateTimeUtc,
pub default_permissions: u64,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub default_server_permissions: i64,
pub default_channel_permissions: i64,
pub default_voice_permissions: i64,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -47,9 +52,23 @@ impl Related<super::server_user::Entity> for Entity {
#[async_trait] #[async_trait]
impl ActiveModelBehavior for ActiveModel { impl ActiveModelBehavior for ActiveModel {
fn new() -> Self { fn new() -> Self {
let default_perm = PermissionSet::DEFAULT;
Self { Self {
id: Set(Uuid::new_v4()), id: Set(Uuid::new_v4()),
default_server_permissions: Set(default_perm.server.bits() as i64),
default_channel_permissions: Set(default_perm.channel.bits() as i64),
default_voice_permissions: Set(default_perm.voice.bits() as i64),
..ActiveModelTrait::default() ..ActiveModelTrait::default()
} }
} }
} }
impl Model {
pub fn default_permissions(&self) -> PermissionSet {
PermissionSet {
server: ServerPermission::from_bits_truncate(self.default_server_permissions as u64),
channel: ChannelPermission::from_bits_truncate(self.default_channel_permissions as u64),
voice: VoicePermission::from_bits_truncate(self.default_voice_permissions as u64),
}
}
}
+5 -1
View File
@@ -16,7 +16,11 @@ pub struct Model {
pub updated_at: DateTimeUtc, pub updated_at: DateTimeUtc,
pub is_admin: bool, pub is_admin: bool,
pub is_owner: bool, pub is_owner: bool,
pub permissions: u64,
/// Permissions serveur par défaut (stockées en i64, lues en u64)
pub server_permissions: i64,
pub channel_permissions: i64,
pub voice_permissions: i64,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+77
View File
@@ -0,0 +1,77 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
/// Permissions liées aux serveurs (gestion globale)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ServerPermission: u64 {
const ManageServer = 1 << 0;
const ManageRoles = 1 << 1;
const KickMember = 1 << 2;
const BanMember = 1 << 3;
// ... jusqu'à 64 perms serveur
}
}
bitflags! {
/// Permissions liées aux canaux (texte + vocal)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ChannelPermission: u64 {
const ReadChannel = 1 << 0;
const SendMessage = 1 << 1;
const DeleteMessage = 1 << 2;
const DeleteOthersMessage = 1 << 3;
const ManageChannel = 1 << 4;
// ...
}
}
bitflags! {
/// Permissions liées au vocal
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VoicePermission: u64 {
const JoinChannel = 1 << 0;
const VoiceSpeak = 1 << 1;
const VoiceMuteOthers = 1 << 2;
const VoiceDeafenOthers = 1 << 3;
// ...
}
}
/// Agrégat de permissions traversant tous les domaines.
/// Utile pour les API et les contrôles complets.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct PermissionSet {
pub server: ServerPermission,
pub channel: ChannelPermission,
pub voice: VoicePermission,
}
impl PermissionSet {
pub const fn new(
server: ServerPermission,
channel: ChannelPermission,
voice: VoicePermission,
) -> Self {
Self {
server,
channel,
voice,
}
}
pub const DEFAULT: Self = Self::new(
ServerPermission::empty(),
ChannelPermission::from_bits_truncate(
ChannelPermission::ReadChannel.bits()
| ChannelPermission::SendMessage.bits()
| ChannelPermission::DeleteMessage.bits(),
),
VoicePermission::from_bits_truncate(
VoicePermission::JoinChannel.bits() | VoicePermission::VoiceSpeak.bits(),
),
);
}
+140
View File
@@ -0,0 +1,140 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)] // Permet de sérialiser comme un simple entier
pub struct Permission: u64 {
/// Pouvoir voir le canal dans la liste et lire les messages
const ReadChannel = 1 << 0;
/// Pouvoir rejoindre le canal vocal
const JoinChannel = 1 << 1;
/// Pouvoir envoyer des messages
const SendMessage = 1 << 2;
/// Pouvoir supprimer ses propres messages
const DeleteMessage = 1 << 3;
/// Pouvoir supprimer les messages des autres (Modérateur)
const DeleteOthersMessage = 1 << 4;
/// Pouvoir modifier les paramètres du canal
const ManageChannel = 1 << 5;
/// Pouvoir gérer les groupes/permissions du serveur
const ManageRoles = 1 << 6;
/// Pouvoir expulser des membres du serveur
const KickMember = 1 << 7;
/// Pouvoir parler en vocal
const VoiceSpeak = 1 << 8;
/// Pouvoir rendre muet les autres utilisateurs en vocal
const VoiceMuteOthers = 1 << 9;
/// Pouvoir modifier les paramètres globaux du serveur
const ManageServer = 1 << 10;
}
}
impl Permission {
// ─── Presets de permissions ────────────────────────────────────────────
/// Permissions par défaut pour un utilisateur standard
pub const DEFAULT: Self = Self::from_bits_truncate(
Self::ReadChannel.bits()
| Self::JoinChannel.bits()
| Self::SendMessage.bits()
| Self::DeleteMessage.bits()
| Self::VoiceSpeak.bits(),
);
/// Permissions de modération (sans gestion serveur)
pub const MODERATOR: Self = Self::from_bits_truncate(
Self::DEFAULT.bits()
| Self::DeleteOthersMessage.bits()
| Self::KickMember.bits()
| Self::VoiceMuteOthers.bits(),
);
/// Permissions d'administrateur (accès complet hormis l'ownership)
pub const ADMIN: Self = Self::from_bits_truncate(
Self::MODERATOR.bits() | Self::ManageChannel.bits() | Self::ManageRoles.bits(),
);
/// Toutes les permissions (équivalent owner)
pub const OWNER: Self = Self::all();
// ─── Groupes logiques ──────────────────────────────────────────────────
/// Toutes les permissions liées au chat
pub const CHAT_PERMISSIONS: Self = Self::from_bits_truncate(
Self::ReadChannel.bits()
| Self::SendMessage.bits()
| Self::DeleteMessage.bits()
| Self::DeleteOthersMessage.bits(),
);
/// Toutes les permissions liées au vocal
pub const VOICE_PERMISSIONS: Self = Self::from_bits_truncate(
Self::JoinChannel.bits() | Self::VoiceSpeak.bits() | Self::VoiceMuteOthers.bits(),
);
/// Toutes les permissions de gestion (Manage*)
pub const MANAGEMENT_PERMISSIONS: Self = Self::from_bits_truncate(
Self::ManageChannel.bits() | Self::ManageRoles.bits() | Self::ManageServer.bits(),
);
// ─── Helpers de vérification ───────────────────────────────────────────
/// Vérifie si l'utilisateur peut effectuer des actions de modération
#[inline]
pub fn is_moderator(&self) -> bool {
self.contains(Self::DeleteOthersMessage) || self.contains(Self::KickMember)
}
/// Vérifie si l'utilisateur a au moins une permission de gestion
#[inline]
pub fn is_admin(&self) -> bool {
self.intersects(Self::MANAGEMENT_PERMISSIONS)
}
/// Vérifie si l'utilisateur a tous les droits (owner)
#[inline]
pub fn is_owner(&self) -> bool {
*self == Self::OWNER
}
/// Vérifie si l'utilisateur peut accéder à un canal vocal
#[inline]
pub fn can_use_voice(&self) -> bool {
self.contains(Self::JoinChannel) && self.contains(Self::VoiceSpeak)
}
/// Vérifie si l'utilisateur peut interagir avec le chat (lire ET écrire)
#[inline]
pub fn can_chat(&self) -> bool {
self.contains(Self::ReadChannel | Self::SendMessage)
}
// ─── Conversion / Sérialisation ────────────────────────────────────────
/// Retourne la liste des noms des permissions actives
/// Utile pour les API REST ou les logs
pub fn to_names(&self) -> Vec<&'static str> {
self.iter_names().map(|(name, _)| name).collect()
}
/// Construit depuis une liste de noms (pour parsing JSON par ex)
pub fn from_names(names: &[&str]) -> Self {
names
.iter()
.filter_map(|name| Self::from_name(name))
.fold(Self::empty(), |acc, p| acc | p)
}
}
impl From<u64> for Permission {
fn from(bits: u64) -> Self {
Self::from_bits_truncate(bits)
}
}
impl From<Permission> for u64 {
fn from(perms: Permission) -> Self {
perms.bits()
}
}
+5
View File
@@ -0,0 +1,5 @@
Ce module permet de :
- Gérer/simplifier les interactions avec la base de données
- Avoir un système de Signal qui se déclenche lors de modifications
dans la base de données (Création, Modification, Suppression)
+46
View File
@@ -0,0 +1,46 @@
use std::sync::Arc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter,
};
use crate::models::category;
use crate::repositories::RepositoryContext;
#[derive(Clone)]
pub struct CategoryRepository {
pub context: Arc<RepositoryContext>
}
impl CategoryRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<category::Model>, DbErr> {
category::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn get_all(&self) -> Result<Vec<category::Model>, DbErr> {
category::Entity::find().all(&self.context.db).await
}
pub async fn get_by_server_id(&self, server_id: uuid::Uuid) -> Result<Vec<category::Model>, DbErr> {
category::Entity::find()
.filter(category::Column::ServerId.eq(server_id))
.all(&self.context.db)
.await
}
pub async fn update(&self, active: category::ActiveModel) -> Result<category::Model, DbErr> {
let category = active.update(&self.context.db).await?;
self.context.events.emit("Category_updated", category.clone());
Ok(category)
}
pub async fn create(&self, active: category::ActiveModel) -> Result<category::Model, DbErr> {
let category = active.insert(&self.context.db).await?;
self.context.events.emit("Category_created", category.clone());
Ok(category)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
category::Entity::delete_by_id(id).exec(&self.context.db).await?;
self.context.events.emit("Category_deleted", id);
Ok(())
}
}
+35
View File
@@ -0,0 +1,35 @@
use crate::models::channel;
use crate::repositories::RepositoryContext;
use sea_orm::{ActiveModelTrait, DbErr, EntityTrait};
use std::sync::Arc;
#[derive(Clone)]
pub struct ChannelRepository {
pub context: Arc<RepositoryContext>,
}
impl ChannelRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<channel::Model>, DbErr> {
channel::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: channel::ActiveModel) -> Result<channel::Model, DbErr> {
let channel = active.update(&self.context.db).await?;
self.context.events.emit("channel_updated", channel.clone());
Ok(channel)
}
pub async fn create(&self, active: channel::ActiveModel) -> Result<channel::Model, DbErr> {
let channel = active.insert(&self.context.db).await?;
self.context.events.emit("channel_created", channel.clone());
Ok(channel)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
channel::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("channel_deleted", id);
Ok(())
}
}
+43
View File
@@ -0,0 +1,43 @@
use crate::models::group;
use crate::repositories::RepositoryContext;
use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct GroupRepository {
pub context: Arc<RepositoryContext>,
}
impl GroupRepository {
pub async fn get_all_by_server(&self, server_id: Uuid) -> Result<Vec<group::Model>, DbErr> {
group::Entity::find()
.filter(group::Column::ServerId.eq(server_id))
.all(&self.context.db)
.await
}
pub async fn get_by_id(&self, id: Uuid) -> Result<Option<group::Model>, DbErr> {
group::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn create(&self, active: group::ActiveModel) -> Result<group::Model, DbErr> {
let group = active.insert(&self.context.db).await?;
self.context.events.emit("group_created", group.clone());
Ok(group)
}
pub async fn update(&self, active: group::ActiveModel) -> Result<group::Model, DbErr> {
let group = active.update(&self.context.db).await?;
self.context.events.emit("group_updated", group.clone());
Ok(group)
}
pub async fn delete(&self, id: Uuid) -> Result<bool, DbErr> {
let res = group::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("group_deleted", id);
Ok(res.rows_affected > 0)
}
}
+33
View File
@@ -0,0 +1,33 @@
use std::sync::Arc;
use sea_orm::{DbErr, EntityTrait, ActiveModelTrait};
use crate::models::message;
use crate::repositories::RepositoryContext;
#[derive(Clone)]
pub struct MessageRepository {
pub context: Arc<RepositoryContext>
}
impl MessageRepository {
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<message::Model>, DbErr> {
message::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: message::ActiveModel) -> Result<message::Model, DbErr> {
let message = active.update(&self.context.db).await?;
self.context.events.emit("message_updated", message.clone());
Ok(message)
}
pub async fn create(&self, active: message::ActiveModel) -> Result<message::Model, DbErr> {
let message = active.insert(&self.context.db).await?;
self.context.events.emit("message_created", message.clone());
Ok(message)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
message::Entity::delete_by_id(id).exec(&self.context.db).await?;
self.context.events.emit("message_deleted", id);
Ok(())
}
}
+63
View File
@@ -0,0 +1,63 @@
use crate::repositories::category::CategoryRepository;
use crate::repositories::channel::ChannelRepository;
use crate::repositories::group::GroupRepository;
use crate::repositories::message::MessageRepository;
use crate::repositories::server::ServerRepository;
use crate::repositories::user::UserRepository;
use event_bus::EventBus;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
mod category;
mod channel;
mod group;
mod message;
mod server;
pub mod types;
mod user;
#[derive(Clone)]
pub struct RepositoryContext {
db: DatabaseConnection,
events: Arc<EventBus>,
}
#[derive(Clone)]
pub struct Repositories {
pub server: ServerRepository,
pub category: CategoryRepository,
pub channel: ChannelRepository,
pub group: GroupRepository,
pub message: MessageRepository,
pub user: UserRepository,
}
impl Repositories {
pub fn new(db: &DatabaseConnection, events: Arc<EventBus>) -> Self {
let context = Arc::new(RepositoryContext {
db: db.clone(),
events,
});
Self {
server: ServerRepository {
context: context.clone(),
},
category: CategoryRepository {
context: context.clone(),
},
channel: ChannelRepository {
context: context.clone(),
},
group: GroupRepository {
context: context.clone(),
},
message: MessageRepository {
context: context.clone(),
},
user: UserRepository {
context: context.clone(),
},
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use super::types::{ServerExplorerItem, ServerTree};
use super::RepositoryContext;
use crate::models::{category, channel, group, server};
use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Clone)]
pub struct ServerRepository {
pub context: Arc<RepositoryContext>,
}
impl ServerRepository {
pub async fn get_all(&self) -> Result<Vec<server::Model>, DbErr> {
server::Entity::find().all(&self.context.db).await
}
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<server::Model>, DbErr> {
server::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn update(&self, active: server::ActiveModel) -> Result<server::Model, DbErr> {
let server = active.update(&self.context.db).await?;
self.context.events.emit("server_updated", server.clone());
Ok(server)
}
pub async fn create(&self, active: server::ActiveModel) -> Result<server::Model, DbErr> {
let server = active.insert(&self.context.db).await?;
// Créer le groupe par défaut pour le serveur
let default_group = group::ActiveModel {
server_id: Set(server.id),
name: Set("Membres".to_string()),
is_default: Set(true),
..Default::default()
};
default_group.insert(&self.context.db).await?;
self.context.events.emit("server_created", server.clone());
Ok(server)
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<bool, DbErr> {
let res = server::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("server_deleted", id);
Ok(res.rows_affected > 0)
}
}
// Helpers
impl ServerRepository {
pub async fn get_tree(&self, server_id: Uuid) -> Result<ServerTree, DbErr> {
// 1. Récupération des catégories avec leurs channels
let categories_with_channels = category::Entity::find()
.filter(category::Column::ServerId.eq(server_id))
.find_with_related(channel::Entity)
.all(&self.context.db)
.await?;
// 2. Récupération des channels orphelins (sans catégorie)
let orphan_channels = channel::Entity::find()
.filter(channel::Column::ServerId.eq(server_id))
.filter(channel::Column::CategoryId.is_null())
.all(&self.context.db)
.await?;
// 3. Transformation et tri des enfants
let mut items: Vec<ServerExplorerItem> = Vec::new();
for (cat, mut channels) in categories_with_channels {
// On trie les channels internes (obligatoire car SQL ne garantit aucun ordre ici)
channels.sort_by(|a, b| {
a.position
.cmp(&b.position)
.then(a.created_at.cmp(&b.created_at))
});
items.push(ServerExplorerItem::Category(cat, channels));
}
for chan in orphan_channels {
items.push(ServerExplorerItem::Channel(chan));
}
// 4. Tri final de la liste globale (Mélange catégories et orphelins)
items.sort_by(|a, b| {
let pos_cmp = a.position().cmp(&b.position());
if pos_cmp == std::cmp::Ordering::Equal {
// Départage par date si position identique
let date_a = match a {
ServerExplorerItem::Category(c, _) => c.created_at,
ServerExplorerItem::Channel(c) => c.created_at,
};
let date_b = match b {
ServerExplorerItem::Category(c, _) => c.created_at,
ServerExplorerItem::Channel(c) => c.created_at,
};
date_a.cmp(&date_b)
} else {
pos_cmp
}
});
Ok(ServerTree { items })
}
}
+20
View File
@@ -0,0 +1,20 @@
use crate::models::{category, channel};
pub enum ServerExplorerItem {
Category(category::Model, Vec<channel::Model>),
Channel(channel::Model),
}
// Pour pouvoir trier facilement
impl ServerExplorerItem {
pub fn position(&self) -> i32 {
match self {
ServerExplorerItem::Category(cat, _) => cat.position,
ServerExplorerItem::Channel(chan) => chan.position,
}
}
}
pub struct ServerTree {
pub items: Vec<ServerExplorerItem>,
}
+88
View File
@@ -0,0 +1,88 @@
use crate::models::user;
use crate::repositories::RepositoryContext;
use crate::utils::password;
use sea_orm::{
ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, PaginatorTrait,
QueryFilter, Set,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct UserRepository {
pub context: Arc<RepositoryContext>,
}
impl UserRepository {
pub async fn get_all(&self) -> Result<Vec<user::Model>, DbErr> {
user::Entity::find().all(&self.context.db).await
}
pub async fn count(&self) -> Result<u64, DbErr> {
user::Entity::find().count(&self.context.db).await
}
pub async fn get_by_id(&self, id: uuid::Uuid) -> Result<Option<user::Model>, DbErr> {
user::Entity::find_by_id(id).one(&self.context.db).await
}
pub async fn get_by_username(&self, username: String) -> Result<Option<user::Model>, DbErr> {
user::Entity::find()
.filter(user::Column::Username.eq(username))
.one(&self.context.db)
.await
}
pub async fn check_password(
&self,
username: String,
password: String,
) -> Result<user::Model, DbErr> {
let user = self.get_by_username(username).await?;
if let Some(user) = user {
let password_ok = password::verify_password(password.as_str(), user.password.as_str())
.map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?;
if password_ok {
return Ok(user);
}
}
Err(DbErr::Custom("Invalid username or password".to_string()))
}
pub async fn update(&self, active: user::ActiveModel) -> Result<user::Model, DbErr> {
let user = active.update(&self.context.db).await?;
self.context.events.emit("user_updated", user.clone());
Ok(user)
}
pub async fn create(&self, active: user::ActiveModel) -> Result<user::Model, DbErr> {
let user = active.insert(&self.context.db).await?;
self.context.events.emit("user_created", user.clone());
Ok(user)
}
pub async fn set_password(&self, id: uuid::Uuid, password: String) -> Result<(), DbErr> {
let user = self
.get_by_id(id)
.await?
.ok_or_else(|| DbErr::Custom("User not found".to_string()))?;
let mut active = user.into_active_model();
let password = password::hash_password(&password)
.map_err(|_| DbErr::Custom("Password hashing failed".to_string()))?;
active.password = Set(password);
let user = self.update(active).await?;
self.context.events.emit("user_changed", user);
Ok(())
}
pub async fn delete(&self, id: uuid::Uuid) -> Result<(), DbErr> {
user::Entity::delete_by_id(id)
.exec(&self.context.db)
.await?;
self.context.events.emit("user_deleted", id);
Ok(())
}
}
+1
View File
@@ -0,0 +1 @@
pub struct Attachment {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateAttachmentRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateAttachmentRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct AttachmentResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Attachment, dto::AttachmentResponse};
pub fn to_response(_item: Attachment) -> AttachmentResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/attachments", get(handlers::get_all).post(handlers::create))
.route(
"/attachments/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Attachment;
pub async fn find_all() -> Vec<Attachment> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Attachment> {
todo!()
}
pub async fn create(_item: Attachment) -> Attachment {
todo!()
}
pub async fn update(_id: u64, _item: Attachment) -> Option<Attachment> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Category {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateCategoryRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateCategoryRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct CategoryResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Category, dto::CategoryResponse};
pub fn to_response(_item: Category) -> CategoryResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/categorys", get(handlers::get_all).post(handlers::create))
.route(
"/categorys/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Category;
pub async fn find_all() -> Vec<Category> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Category> {
todo!()
}
pub async fn create(_item: Category) -> Category {
todo!()
}
pub async fn update(_id: u64, _item: Category) -> Option<Category> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Channel {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateChannelRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateChannelRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct ChannelResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Channel, dto::ChannelResponse};
pub fn to_response(_item: Channel) -> ChannelResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/channels", get(handlers::get_all).post(handlers::create))
.route(
"/channels/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Channel;
pub async fn find_all() -> Vec<Channel> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Channel> {
todo!()
}
pub async fn create(_item: Channel) -> Channel {
todo!()
}
pub async fn update(_id: u64, _item: Channel) -> Option<Channel> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Group {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateGroupRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateGroupRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct GroupResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Group, dto::GroupResponse};
pub fn to_response(_item: Group) -> GroupResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/groups", get(handlers::get_all).post(handlers::create))
.route(
"/groups/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Group;
pub async fn find_all() -> Vec<Group> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Group> {
todo!()
}
pub async fn create(_item: Group) -> Group {
todo!()
}
pub async fn update(_id: u64, _item: Group) -> Option<Group> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct Message {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateMessageRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateMessageRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Message, dto::MessageResponse};
pub fn to_response(_item: Message) -> MessageResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/messages", get(handlers::get_all).post(handlers::create))
.route(
"/messages/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Message;
pub async fn find_all() -> Vec<Message> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Message> {
todo!()
}
pub async fn create(_item: Message) -> Message {
todo!()
}
pub async fn update(_id: u64, _item: Message) -> Option<Message> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+20
View File
@@ -0,0 +1,20 @@
use axum::Router;
pub mod attachment;
pub mod category;
pub mod channel;
pub mod group;
pub mod message;
pub mod server;
pub mod user;
pub fn router() -> Router {
Router::new()
.merge(user::routes::router())
.merge(server::routes::router())
.merge(channel::routes::router())
.merge(message::routes::router())
.merge(group::routes::router())
.merge(category::routes::router())
.merge(attachment::routes::router())
}
+1
View File
@@ -0,0 +1 @@
pub struct Server {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateServerRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateServerRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::Server, dto::ServerResponse};
pub fn to_response(_item: Server) -> ServerResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{Router, routing::get};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/servers", get(handlers::get_all).post(handlers::create))
.route(
"/servers/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::Server;
pub async fn find_all() -> Vec<Server> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<Server> {
todo!()
}
pub async fn create(_item: Server) -> Server {
todo!()
}
pub async fn update(_id: u64, _item: Server) -> Option<Server> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub struct User {}
+10
View File
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateUserRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateUserRequest {}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserResponse {}
+22
View File
@@ -0,0 +1,22 @@
use axum::http::StatusCode;
use axum::response::IntoResponse;
pub async fn get_all() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn get_by_id() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn create() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn update() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
pub async fn delete() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
+5
View File
@@ -0,0 +1,5 @@
use super::{domain::User, dto::UserResponse};
pub fn to_response(_user: User) -> UserResponse {
todo!()
}
+6
View File
@@ -0,0 +1,6 @@
pub mod domain;
pub mod dto;
pub mod handlers;
pub mod mapper;
pub mod routes;
pub mod service;
+14
View File
@@ -0,0 +1,14 @@
use axum::{routing::get, Router};
use super::handlers;
pub fn router() -> Router {
Router::new()
.route("/users", get(handlers::get_all).post(handlers::create))
.route(
"/users/:id",
get(handlers::get_by_id)
.put(handlers::update)
.delete(handlers::delete),
)
}
+21
View File
@@ -0,0 +1,21 @@
use super::domain::User;
pub async fn find_all() -> Vec<User> {
todo!()
}
pub async fn find_by_id(_id: u64) -> Option<User> {
todo!()
}
pub async fn create(_user: User) -> User {
todo!()
}
pub async fn update(_id: u64, _user: User) -> Option<User> {
todo!()
}
pub async fn delete(_id: u64) -> bool {
todo!()
}
+1
View File
@@ -0,0 +1 @@
pub mod password;
+25
View File
@@ -0,0 +1,25 @@
use argon2::{
password_hash::{phc::PasswordHash, PasswordHasher, PasswordVerifier}, Algorithm, Argon2, Params,
Version,
};
/// Hache un password avec Argon2id
/// Génère automatiquement un salt cryptographiquement sûr
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let params = Params::new(65540, 18, 1, None)?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
argon2
.hash_password(password.as_bytes())
.map(|hash| hash.to_string())
}
/// Vérifie un password contre son hash
pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
+348
View File
@@ -0,0 +1,348 @@
# Projet : Runtime de Signaux/Slots Haute Performance en Rust
## Objectif
Créer un runtime événementiel inspiré de Qt Signals/Slots mais pensé pour :
- haute fréquence,
- multi-thread,
- audio temps réel,
- architecture modulaire,
- runtime distribué par workers,
- typage fort compile-time,
- signaux async/sync,
- performance extrême.
Le runtime doit pouvoir servir de base à :
- moteur événementiel,
- runtime applicatif,
- scheduler coopératif,
- actor system,
- infrastructure temps réel.
IMPORTANT :
Le runtime doit rester générique et indépendant du réseau.
Aucune logique UDP/TCP/socket ne doit être implémentée automatiquement.
Lintégration réseau sera réalisée manuellement plus tard par le développeur.
---
# Vision Architecture
```text
Application
Signal Runtime
├── Worker 0
│ ├── Event Loop
│ ├── Audio Systems
│ ├── Metrics
│ └── Channels
├── Worker 1
│ ├── Auth
│ ├── Sessions
│ └── Runtime Tasks
└── Worker N
PHASE 1 — Runtime Minimal
Objectif
Créer :
runtime global,
workers,
queues,
event loop,
dispatch d’événements.
A implémenter
Runtime
struct Runtime {
workers: Vec<Worker>,
}
Worker
struct Worker {
id: usize,
sender: Sender<Event>,
}
Event Loop
Boucle infinie :
loop {
let event = rx.recv().unwrap();
event.execute();
}
Event Trait
trait ExecutableEvent {
fn execute(self: Box<Self>);
}
PHASE 2 — Système de Signaux
Objectif
Créer :
Signal<T>,
connect(),
emit(),
subscribers,
callbacks dynamiques.
A implémenter
Signal générique
struct Signal<T> {
subscribers: Vec<Subscriber<T>>,
}
Subscriber
Contient :
callback,
target_worker,
connection_type.
Connection Types
enum ConnectionType {
Direct,
Queued,
}
Emit
Le signal :
ne doit jamais exécuter directement un callback queued,
doit créer un Event,
envoyer vers le worker cible.
PHASE 3 — Runtime Global
Objectif
Créer :
runtime singleton,
accès global,
routing automatique.
A implémenter
OnceLock
static RUNTIME: OnceLock<Runtime>;
Dispatch global
runtime.dispatch(worker_id, event);
Routing intelligent
Le runtime doit :
choisir le bon worker,
envoyer dans la bonne queue,
éviter les locks globaux.
PHASE 4 — Proc Macros
Objectif
Créer :
#[derive(Signals)],
auto-enregistrement,
génération automatique de metadata.
Crate séparée
signals_derive/
Technologies
syn
quote
proc_macro2
Features
Détection automatique
Détecter :
Signal<T>
dans une struct.
Génération
Générer :
impl AudioEngine {
pub fn register_signals(...)
}
Support futur
Prévoir :
#[signal]
metadata runtime,
introspection légère.
PHASE 5 — Thread Affinity
Objectif
Chaque objet appartient à un worker.
A implémenter
RuntimeObject
struct RuntimeObject {
worker_id: usize,
}
Règles
les signaux queued doivent sexécuter sur le worker cible,
éviter les accès cross-thread directs.
PHASE 6 — Weak References
Objectif
Reproduire lauto-cleanup de Qt.
A implémenter
Utiliser :
Arc<T>
Weak<T>
Comportement
Si le receiver est détruit :
la connexion devient invalide,
le runtime lignore automatiquement.
PHASE 7 — Event Bus Typé
Objectif
Permettre :
runtime.emit(EventType)
runtime.subscribe::<EventType>()
Contraintes
typage compile-time,
pas de string routing obligatoire,
support Any/TypeId.
PHASE 8 — Optimisation Performance
Objectif
Préparer haute fréquence.
Optimisations attendues
Eviter
Mutex globaux,
allocations fréquentes,
Box inutiles,
contention CPU.
Ajouter
queues lock-free,
batching,
event pooling,
cache locality,
sharding runtime.
PHASE 9 — Runtime Sharding
Objectif
Eviter le bottleneck central.
Architecture
Shard 0
Shard 1
Shard 2
Chaque shard :
possède ses subscribers,
possède ses queues,
possède ses workers.
PHASE 10 — Intégration Applicative Générique
Objectif
Permettre à des systèmes externes de pousser des événements dans le runtime.
IMPORTANT :
Le runtime ne doit contenir aucune logique réseau native.
Il doit uniquement exposer des APIs génériques permettant :
d’émettre des événements,
de sabonner à des événements,
de router les tâches.
Les couches réseau, audio, fichiers, protocoles ou IO seront branchées manuellement par le développeur.
Exemple attendu
runtime.emit(MyCustomEvent {
...
});
PHASE 11 — Scheduler Coopératif
Objectif
Fusionner :
signaux,
scheduler,
tâches coopératives.
Inspiré de
Go runtime,
Tokio,
Erlang.
Features
spawn()
spawn(async move {})
sleep()
wakeup()
channels()
PHASE 12 — Async Runtime Integration
Objectif
Compatibilité :
Tokio,
async/await,
futures.
Support
Async subscribers
signal.connect(async move |event| {
...
});
PHASE 13 — Monitoring & Introspection
Objectif
Observer :
workers,
queues,
charge CPU,
nombre d’événements,
latence.
Metrics
events/sec,
queue depth,
dropped events,
worker usage.
PHASE 14 — API Finale
Objectif
API élégante type Qt.
Exemple cible
#[derive(Signals)]
struct AudioEngine {
packet_received: Signal<AudioPacket>,
}
audio.packet_received.connect(&decoder, |d, p| {
d.decode(p);
});
audio.packet_received.emit(packet);
Contraintes Techniques
Langage
Rust stable uniquement au début.
Dépendances possibles
crossbeam
flume
parking_lot
dashmap (optionnel)
tokio (plus tard)
Eviter au début
async partout,
macros trop complexes,
ECS complet,
reflection lourde.
Objectif Long Terme
Créer un runtime capable de :
gérer des centaines de milliers/millions d’événements,
servir de cœur à des applications temps réel,
fournir un scheduler coopératif,
remplacer une architecture classique callbacks/mutex.