From 0b441b07595f93f133106415d93eeabb960b9601 Mon Sep 17 00:00:00 2001 From: Nell Date: Sun, 10 May 2026 03:16:13 +0200 Subject: [PATCH] Init --- Cargo.lock | 372 ++++++++++++++++-- Cargo.toml | 6 +- .../src/m20220101_000001_create_table.rs | 59 ++- src/core/mod.rs | 33 ++ src/core/state.rs | 9 + src/database/database.rs | 10 +- src/http/mod.rs | 1 + src/http/server.rs | 23 ++ src/lib.rs | 7 + src/main.rs | 11 +- src/models/group.rs | 6 +- src/models/server.rs | 21 +- src/models/server_user.rs | 6 +- src/permissions.rs | 77 ++++ src/permissions_old.rs | 140 +++++++ src/repositories/README.md | 5 + src/repositories/category.rs | 46 +++ src/repositories/channel.rs | 35 ++ src/repositories/group.rs | 43 ++ src/repositories/message.rs | 33 ++ src/repositories/mod.rs | 63 +++ src/repositories/server.rs | 110 ++++++ src/repositories/types.rs | 20 + src/repositories/user.rs | 88 +++++ src/routes/attachment/domain.rs | 1 + src/routes/attachment/dto.rs | 10 + src/routes/attachment/handlers.rs | 22 ++ src/routes/attachment/mapper.rs | 5 + src/routes/attachment/mod.rs | 6 + src/routes/attachment/routes.rs | 14 + src/routes/attachment/service.rs | 21 + src/routes/category/domain.rs | 1 + src/routes/category/dto.rs | 10 + src/routes/category/handlers.rs | 22 ++ src/routes/category/mapper.rs | 5 + src/routes/category/mod.rs | 6 + src/routes/category/routes.rs | 14 + src/routes/category/service.rs | 21 + src/routes/channel/domain.rs | 1 + src/routes/channel/dto.rs | 10 + src/routes/channel/handlers.rs | 22 ++ src/routes/channel/mapper.rs | 5 + src/routes/channel/mod.rs | 6 + src/routes/channel/routes.rs | 14 + src/routes/channel/service.rs | 21 + src/routes/group/domain.rs | 1 + src/routes/group/dto.rs | 10 + src/routes/group/handlers.rs | 22 ++ src/routes/group/mapper.rs | 5 + src/routes/group/mod.rs | 6 + src/routes/group/routes.rs | 14 + src/routes/group/service.rs | 21 + src/routes/message/domain.rs | 1 + src/routes/message/dto.rs | 10 + src/routes/message/handlers.rs | 22 ++ src/routes/message/mapper.rs | 5 + src/routes/message/mod.rs | 6 + src/routes/message/routes.rs | 14 + src/routes/message/service.rs | 21 + src/routes/mod.rs | 20 + src/routes/server/domain.rs | 1 + src/routes/server/dto.rs | 10 + src/routes/server/handlers.rs | 22 ++ src/routes/server/mapper.rs | 5 + src/routes/server/mod.rs | 6 + src/routes/server/routes.rs | 14 + src/routes/server/service.rs | 21 + src/routes/user/domain.rs | 1 + src/routes/user/dto.rs | 10 + src/routes/user/handlers.rs | 22 ++ src/routes/user/mapper.rs | 5 + src/routes/user/mod.rs | 6 + src/routes/user/routes.rs | 14 + src/routes/user/service.rs | 21 + src/utils/mod.rs | 1 + src/utils/password.rs | 25 ++ test | 348 ++++++++++++++++ 77 files changed, 2100 insertions(+), 71 deletions(-) create mode 100644 src/core/mod.rs create mode 100644 src/core/state.rs create mode 100644 src/http/mod.rs create mode 100644 src/http/server.rs create mode 100644 src/permissions.rs create mode 100644 src/permissions_old.rs create mode 100644 src/repositories/README.md create mode 100644 src/repositories/category.rs create mode 100644 src/repositories/channel.rs create mode 100644 src/repositories/group.rs create mode 100644 src/repositories/message.rs create mode 100644 src/repositories/mod.rs create mode 100644 src/repositories/server.rs create mode 100644 src/repositories/types.rs create mode 100644 src/repositories/user.rs create mode 100644 src/routes/attachment/domain.rs create mode 100644 src/routes/attachment/dto.rs create mode 100644 src/routes/attachment/handlers.rs create mode 100644 src/routes/attachment/mapper.rs create mode 100644 src/routes/attachment/mod.rs create mode 100644 src/routes/attachment/routes.rs create mode 100644 src/routes/attachment/service.rs create mode 100644 src/routes/category/domain.rs create mode 100644 src/routes/category/dto.rs create mode 100644 src/routes/category/handlers.rs create mode 100644 src/routes/category/mapper.rs create mode 100644 src/routes/category/mod.rs create mode 100644 src/routes/category/routes.rs create mode 100644 src/routes/category/service.rs create mode 100644 src/routes/channel/domain.rs create mode 100644 src/routes/channel/dto.rs create mode 100644 src/routes/channel/handlers.rs create mode 100644 src/routes/channel/mapper.rs create mode 100644 src/routes/channel/mod.rs create mode 100644 src/routes/channel/routes.rs create mode 100644 src/routes/channel/service.rs create mode 100644 src/routes/group/domain.rs create mode 100644 src/routes/group/dto.rs create mode 100644 src/routes/group/handlers.rs create mode 100644 src/routes/group/mapper.rs create mode 100644 src/routes/group/mod.rs create mode 100644 src/routes/group/routes.rs create mode 100644 src/routes/group/service.rs create mode 100644 src/routes/message/domain.rs create mode 100644 src/routes/message/dto.rs create mode 100644 src/routes/message/handlers.rs create mode 100644 src/routes/message/mapper.rs create mode 100644 src/routes/message/mod.rs create mode 100644 src/routes/message/routes.rs create mode 100644 src/routes/message/service.rs create mode 100644 src/routes/mod.rs create mode 100644 src/routes/server/domain.rs create mode 100644 src/routes/server/dto.rs create mode 100644 src/routes/server/handlers.rs create mode 100644 src/routes/server/mapper.rs create mode 100644 src/routes/server/mod.rs create mode 100644 src/routes/server/routes.rs create mode 100644 src/routes/server/service.rs create mode 100644 src/routes/user/domain.rs create mode 100644 src/routes/user/dto.rs create mode 100644 src/routes/user/handlers.rs create mode 100644 src/routes/user/mapper.rs create mode 100644 src/routes/user/mod.rs create mode 100644 src/routes/user/routes.rs create mode 100644 src/routes/user/service.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/password.rs create mode 100644 test diff --git a/Cargo.lock b/Cargo.lock index 434b761..c4dd26c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,18 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "arraydeque" version = "0.5.1" @@ -478,6 +490,58 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "base64" version = "0.22.1" @@ -525,6 +589,15 @@ dependencies = [ "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]] name = "block-buffer" version = "0.10.4" @@ -534,6 +607,15 @@ dependencies = [ "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]] name = "blocking" version = "1.6.2" @@ -619,9 +701,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "shlex", @@ -729,6 +811,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "colorchoice" version = "1.0.5" @@ -834,9 +922,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "criterion" @@ -924,6 +1012,24 @@ dependencies = [ "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]] name = "darling" version = "0.20.11" @@ -1007,12 +1113,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "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]] name = "displaydoc" version = "0.2.5" @@ -1430,7 +1547,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1442,6 +1559,95 @@ dependencies = [ "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]] name = "iana-time-zone" version = "0.1.65" @@ -1573,9 +1779,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1645,9 +1851,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -1749,9 +1955,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1768,7 +1974,7 @@ dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.7.4", + "redox_syscall 0.7.5", ] [[package]] @@ -1832,6 +2038,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -1839,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -1865,6 +2077,12 @@ dependencies = [ "sea-orm-migration", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "1.2.0" @@ -2034,9 +2252,11 @@ dependencies = [ name = "oxspeak_server" version = "0.1.0" dependencies = [ + "argon2", + "axum", + "bitflags", "config", "event_bus", - "glob", "log", "migration", "parking_lot", @@ -2091,6 +2311,16 @@ dependencies = [ "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]] name = "pathdiff" version = "0.2.3" @@ -2164,6 +2394,17 @@ dependencies = [ "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]] name = "pin-project-lite" version = "0.2.17" @@ -2484,9 +2725,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" dependencies = [ "bitflags", ] @@ -2579,7 +2820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", - "digest", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -2604,9 +2845,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.41.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995" dependencies = [ "arrayvec", "borsh", @@ -2907,6 +3148,17 @@ dependencies = [ "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]] name = "serde_spanned" version = "1.1.1" @@ -2936,7 +3188,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -2947,7 +3199,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -2981,7 +3233,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -3137,7 +3389,7 @@ dependencies = [ "bytes", "chrono", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -3301,6 +3553,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.13.2" @@ -3424,9 +3682,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.2" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3512,6 +3770,34 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "tracing" version = "0.1.44" @@ -3658,9 +3944,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" dependencies = [ "indexmap", "serde", @@ -3670,9 +3956,9 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" dependencies = [ "proc-macro2", "quote", @@ -3759,9 +4045,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -3773,9 +4059,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3783,9 +4069,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3793,9 +4079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -3806,9 +4092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -3849,9 +4135,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 6d0b55e..8bf1dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ crate-type = ["rlib"] members = [".", "migration", "event_bus"] [dependencies] -tokio = { version = "1.52.2", features = ["full"] } +tokio = { version = "1.52.3", features = ["full"] } +axum = "0.8" 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"] } migration = { path = "migration" } @@ -26,4 +27,5 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "time"] thiserror = "2" utoipa = { version = "5", features = ["uuid"] } log = "0.4" -glob = "0.3" +bitflags = "2.11.1" +argon2 = { version = "0.6.0-rc.8", features = ["password-hash"] } \ No newline at end of file diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 11b08df..7b8a763 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -28,7 +28,19 @@ impl MigrationTrait for Migration { .default(Expr::current_timestamp()), ) .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() .not_null() .default(0), @@ -120,11 +132,6 @@ impl MigrationTrait for Migration { .not_null() .default(Expr::current_timestamp()), ) - .col( - ColumnDef::new(Alias::new("default_permissions")) - .big_integer() - .null(), - ) // Indexes créés après .foreign_key( ForeignKey::create() @@ -317,7 +324,19 @@ impl MigrationTrait for Migration { .default(false), ) .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() .not_null() .default(0), @@ -361,12 +380,6 @@ impl MigrationTrait for Migration { .not_null() .default("member".to_owned()), ) - .col( - ColumnDef::new(Alias::new("permissions")) - .big_integer() - .not_null() - .default(0), - ) .col( ColumnDef::new(Alias::new("joined_at")) .timestamp_with_time_zone() @@ -553,7 +566,19 @@ impl MigrationTrait for Migration { .col(ColumnDef::new("server_id").uuid().not_null()) .col(ColumnDef::new("name").string().not_null()) .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() .not_null() .default(0), @@ -570,6 +595,12 @@ impl MigrationTrait for Migration { .not_null() .default(Expr::current_timestamp()), ) + .col( + ColumnDef::new("edited_at") + .timestamp_with_time_zone() + .null() + .default(Expr::current_timestamp()), + ) .foreign_key( ForeignKey::create() .name("fk_group_server") diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..6f081f0 --- /dev/null +++ b/src/core/mod.rs @@ -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> { + 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> { + tracing::info!("Starting services..."); + + tokio::select! {} + } +} diff --git a/src/core/state.rs b/src/core/state.rs new file mode 100644 index 0000000..a92f449 --- /dev/null +++ b/src/core/state.rs @@ -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, +} diff --git a/src/database/database.rs b/src/database/database.rs index 20752af..8ea4649 100644 --- a/src/database/database.rs +++ b/src/database/database.rs @@ -1,6 +1,6 @@ -use std::time::Duration; use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection, DbErr}; -use migration::{Migrator, MigratorTrait}; +use std::time::Duration; + #[derive(Clone)] pub struct Database { @@ -19,10 +19,6 @@ impl Database { 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 }) } @@ -31,4 +27,4 @@ impl Database { pub fn get_connection(&self) -> &DatabaseConnection { &self.connection } -} \ No newline at end of file +} diff --git a/src/http/mod.rs b/src/http/mod.rs new file mode 100644 index 0000000..74f47ad --- /dev/null +++ b/src/http/mod.rs @@ -0,0 +1 @@ +pub mod server; diff --git a/src/http/server.rs b/src/http/server.rs new file mode 100644 index 0000000..a92ca9c --- /dev/null +++ b/src/http/server.rs @@ -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> { + 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(()) +} diff --git a/src/lib.rs b/src/lib.rs index fcc8efa..36247a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,11 @@ pub mod config; pub mod database; +pub mod http; pub mod models; +// pub mod permissions_old; +pub mod core; +pub mod permissions; +pub mod repositories; +pub mod routes; pub mod udp; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 672c477..fcc900c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ +use migration::{Migrator, MigratorTrait}; use oxspeak_server_lib::config::AppConfig; +use oxspeak_server_lib::core::App; +use oxspeak_server_lib::database::Database; #[tokio::main] async fn main() -> Result<(), Box> { @@ -19,10 +22,14 @@ async fn main() -> Result<(), Box> { AppConfig::gen_config()?; tracing::info!(config_path = "config.toml", "Generated"); tracing::info!("Config generated, please edit config.toml"); - return Ok(()) + return Ok(()); } let config = AppConfig::load()?; tracing::info!(?config, "Loaded config"); + // 3. Build & Run + let app = App::build(config).await?; + app.run().await; + Ok(()) -} \ No newline at end of file +} diff --git a/src/models/group.rs b/src/models/group.rs index 821e1ce..cb8cf5d 100644 --- a/src/models/group.rs +++ b/src/models/group.rs @@ -11,9 +11,13 @@ pub struct Model { pub id: Uuid, pub server_id: Uuid, pub name: String, - pub permissions: u64, pub is_default: bool, 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)] diff --git a/src/models/server.rs b/src/models/server.rs index 17e37d4..130cd0e 100644 --- a/src/models/server.rs +++ b/src/models/server.rs @@ -1,5 +1,6 @@ //! `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::prelude::async_trait::async_trait; use sea_orm::Set; @@ -13,7 +14,11 @@ pub struct Model { pub password: Option, pub created_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)] @@ -47,9 +52,23 @@ impl Related for Entity { #[async_trait] impl ActiveModelBehavior for ActiveModel { fn new() -> Self { + let default_perm = PermissionSet::DEFAULT; Self { 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() } } } + +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), + } + } +} diff --git a/src/models/server_user.rs b/src/models/server_user.rs index fa70190..21a661d 100644 --- a/src/models/server_user.rs +++ b/src/models/server_user.rs @@ -16,7 +16,11 @@ pub struct Model { pub updated_at: DateTimeUtc, pub is_admin: 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)] diff --git a/src/permissions.rs b/src/permissions.rs new file mode 100644 index 0000000..3280901 --- /dev/null +++ b/src/permissions.rs @@ -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(), + ), + ); +} diff --git a/src/permissions_old.rs b/src/permissions_old.rs new file mode 100644 index 0000000..eda463e --- /dev/null +++ b/src/permissions_old.rs @@ -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 for Permission { + fn from(bits: u64) -> Self { + Self::from_bits_truncate(bits) + } +} + +impl From for u64 { + fn from(perms: Permission) -> Self { + perms.bits() + } +} diff --git a/src/repositories/README.md b/src/repositories/README.md new file mode 100644 index 0000000..b91aad9 --- /dev/null +++ b/src/repositories/README.md @@ -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) \ No newline at end of file diff --git a/src/repositories/category.rs b/src/repositories/category.rs new file mode 100644 index 0000000..1c2137b --- /dev/null +++ b/src/repositories/category.rs @@ -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 +} + +impl CategoryRepository { + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { + category::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn get_all(&self) -> Result, DbErr> { + category::Entity::find().all(&self.context.db).await + } + + pub async fn get_by_server_id(&self, server_id: uuid::Uuid) -> Result, 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 { + 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 { + 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(()) + } +} \ No newline at end of file diff --git a/src/repositories/channel.rs b/src/repositories/channel.rs new file mode 100644 index 0000000..f35047b --- /dev/null +++ b/src/repositories/channel.rs @@ -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, +} + +impl ChannelRepository { + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { + channel::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn update(&self, active: channel::ActiveModel) -> Result { + 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 { + 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(()) + } +} diff --git a/src/repositories/group.rs b/src/repositories/group.rs new file mode 100644 index 0000000..d602842 --- /dev/null +++ b/src/repositories/group.rs @@ -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, +} + +impl GroupRepository { + pub async fn get_all_by_server(&self, server_id: Uuid) -> Result, 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, DbErr> { + group::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn create(&self, active: group::ActiveModel) -> Result { + 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 { + 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 { + 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) + } +} diff --git a/src/repositories/message.rs b/src/repositories/message.rs new file mode 100644 index 0000000..7b8dd6c --- /dev/null +++ b/src/repositories/message.rs @@ -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 +} + +impl MessageRepository { + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { + message::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn update(&self, active: message::ActiveModel) -> Result { + 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 { + 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(()) + } +} \ No newline at end of file diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs new file mode 100644 index 0000000..4a044ce --- /dev/null +++ b/src/repositories/mod.rs @@ -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, +} + +#[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) -> 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(), + }, + } + } +} diff --git a/src/repositories/server.rs b/src/repositories/server.rs new file mode 100644 index 0000000..9b1be9f --- /dev/null +++ b/src/repositories/server.rs @@ -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, +} + +impl ServerRepository { + pub async fn get_all(&self) -> Result, DbErr> { + server::Entity::find().all(&self.context.db).await + } + + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { + server::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn update(&self, active: server::ActiveModel) -> Result { + 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 { + 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 { + 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 { + // 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 = 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 }) + } +} diff --git a/src/repositories/types.rs b/src/repositories/types.rs new file mode 100644 index 0000000..c976bc7 --- /dev/null +++ b/src/repositories/types.rs @@ -0,0 +1,20 @@ +use crate::models::{category, channel}; + +pub enum ServerExplorerItem { + Category(category::Model, Vec), + 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, +} \ No newline at end of file diff --git a/src/repositories/user.rs b/src/repositories/user.rs new file mode 100644 index 0000000..0dafe5b --- /dev/null +++ b/src/repositories/user.rs @@ -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, +} + +impl UserRepository { + pub async fn get_all(&self) -> Result, DbErr> { + user::Entity::find().all(&self.context.db).await + } + + pub async fn count(&self) -> Result { + user::Entity::find().count(&self.context.db).await + } + + pub async fn get_by_id(&self, id: uuid::Uuid) -> Result, DbErr> { + user::Entity::find_by_id(id).one(&self.context.db).await + } + + pub async fn get_by_username(&self, username: String) -> Result, 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 { + 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 { + 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 { + 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(()) + } +} diff --git a/src/routes/attachment/domain.rs b/src/routes/attachment/domain.rs new file mode 100644 index 0000000..719ebaf --- /dev/null +++ b/src/routes/attachment/domain.rs @@ -0,0 +1 @@ +pub struct Attachment {} diff --git a/src/routes/attachment/dto.rs b/src/routes/attachment/dto.rs new file mode 100644 index 0000000..68b1fcb --- /dev/null +++ b/src/routes/attachment/dto.rs @@ -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 {} diff --git a/src/routes/attachment/handlers.rs b/src/routes/attachment/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/attachment/handlers.rs @@ -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 +} diff --git a/src/routes/attachment/mapper.rs b/src/routes/attachment/mapper.rs new file mode 100644 index 0000000..33f587e --- /dev/null +++ b/src/routes/attachment/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Attachment, dto::AttachmentResponse}; + +pub fn to_response(_item: Attachment) -> AttachmentResponse { + todo!() +} diff --git a/src/routes/attachment/mod.rs b/src/routes/attachment/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/attachment/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/attachment/routes.rs b/src/routes/attachment/routes.rs new file mode 100644 index 0000000..be1297c --- /dev/null +++ b/src/routes/attachment/routes.rs @@ -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), + ) +} diff --git a/src/routes/attachment/service.rs b/src/routes/attachment/service.rs new file mode 100644 index 0000000..aadd751 --- /dev/null +++ b/src/routes/attachment/service.rs @@ -0,0 +1,21 @@ +use super::domain::Attachment; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Attachment) -> Attachment { + todo!() +} + +pub async fn update(_id: u64, _item: Attachment) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/category/domain.rs b/src/routes/category/domain.rs new file mode 100644 index 0000000..483dcd0 --- /dev/null +++ b/src/routes/category/domain.rs @@ -0,0 +1 @@ +pub struct Category {} diff --git a/src/routes/category/dto.rs b/src/routes/category/dto.rs new file mode 100644 index 0000000..1cc8a3c --- /dev/null +++ b/src/routes/category/dto.rs @@ -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 {} diff --git a/src/routes/category/handlers.rs b/src/routes/category/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/category/handlers.rs @@ -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 +} diff --git a/src/routes/category/mapper.rs b/src/routes/category/mapper.rs new file mode 100644 index 0000000..4336f3f --- /dev/null +++ b/src/routes/category/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Category, dto::CategoryResponse}; + +pub fn to_response(_item: Category) -> CategoryResponse { + todo!() +} diff --git a/src/routes/category/mod.rs b/src/routes/category/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/category/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/category/routes.rs b/src/routes/category/routes.rs new file mode 100644 index 0000000..4eaccac --- /dev/null +++ b/src/routes/category/routes.rs @@ -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), + ) +} diff --git a/src/routes/category/service.rs b/src/routes/category/service.rs new file mode 100644 index 0000000..e1597a6 --- /dev/null +++ b/src/routes/category/service.rs @@ -0,0 +1,21 @@ +use super::domain::Category; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Category) -> Category { + todo!() +} + +pub async fn update(_id: u64, _item: Category) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/channel/domain.rs b/src/routes/channel/domain.rs new file mode 100644 index 0000000..9984429 --- /dev/null +++ b/src/routes/channel/domain.rs @@ -0,0 +1 @@ +pub struct Channel {} diff --git a/src/routes/channel/dto.rs b/src/routes/channel/dto.rs new file mode 100644 index 0000000..5f0fe50 --- /dev/null +++ b/src/routes/channel/dto.rs @@ -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 {} diff --git a/src/routes/channel/handlers.rs b/src/routes/channel/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/channel/handlers.rs @@ -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 +} diff --git a/src/routes/channel/mapper.rs b/src/routes/channel/mapper.rs new file mode 100644 index 0000000..e9a120e --- /dev/null +++ b/src/routes/channel/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Channel, dto::ChannelResponse}; + +pub fn to_response(_item: Channel) -> ChannelResponse { + todo!() +} diff --git a/src/routes/channel/mod.rs b/src/routes/channel/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/channel/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/channel/routes.rs b/src/routes/channel/routes.rs new file mode 100644 index 0000000..0cb2e83 --- /dev/null +++ b/src/routes/channel/routes.rs @@ -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), + ) +} diff --git a/src/routes/channel/service.rs b/src/routes/channel/service.rs new file mode 100644 index 0000000..27731fd --- /dev/null +++ b/src/routes/channel/service.rs @@ -0,0 +1,21 @@ +use super::domain::Channel; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Channel) -> Channel { + todo!() +} + +pub async fn update(_id: u64, _item: Channel) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/group/domain.rs b/src/routes/group/domain.rs new file mode 100644 index 0000000..b2ea9a9 --- /dev/null +++ b/src/routes/group/domain.rs @@ -0,0 +1 @@ +pub struct Group {} diff --git a/src/routes/group/dto.rs b/src/routes/group/dto.rs new file mode 100644 index 0000000..a6de477 --- /dev/null +++ b/src/routes/group/dto.rs @@ -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 {} diff --git a/src/routes/group/handlers.rs b/src/routes/group/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/group/handlers.rs @@ -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 +} diff --git a/src/routes/group/mapper.rs b/src/routes/group/mapper.rs new file mode 100644 index 0000000..3c5f014 --- /dev/null +++ b/src/routes/group/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Group, dto::GroupResponse}; + +pub fn to_response(_item: Group) -> GroupResponse { + todo!() +} diff --git a/src/routes/group/mod.rs b/src/routes/group/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/group/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/group/routes.rs b/src/routes/group/routes.rs new file mode 100644 index 0000000..c922d72 --- /dev/null +++ b/src/routes/group/routes.rs @@ -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), + ) +} diff --git a/src/routes/group/service.rs b/src/routes/group/service.rs new file mode 100644 index 0000000..903dc0f --- /dev/null +++ b/src/routes/group/service.rs @@ -0,0 +1,21 @@ +use super::domain::Group; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Group) -> Group { + todo!() +} + +pub async fn update(_id: u64, _item: Group) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/message/domain.rs b/src/routes/message/domain.rs new file mode 100644 index 0000000..1a8f629 --- /dev/null +++ b/src/routes/message/domain.rs @@ -0,0 +1 @@ +pub struct Message {} diff --git a/src/routes/message/dto.rs b/src/routes/message/dto.rs new file mode 100644 index 0000000..e94f65f --- /dev/null +++ b/src/routes/message/dto.rs @@ -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 {} diff --git a/src/routes/message/handlers.rs b/src/routes/message/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/message/handlers.rs @@ -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 +} diff --git a/src/routes/message/mapper.rs b/src/routes/message/mapper.rs new file mode 100644 index 0000000..d121559 --- /dev/null +++ b/src/routes/message/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Message, dto::MessageResponse}; + +pub fn to_response(_item: Message) -> MessageResponse { + todo!() +} diff --git a/src/routes/message/mod.rs b/src/routes/message/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/message/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/message/routes.rs b/src/routes/message/routes.rs new file mode 100644 index 0000000..f5833d0 --- /dev/null +++ b/src/routes/message/routes.rs @@ -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), + ) +} diff --git a/src/routes/message/service.rs b/src/routes/message/service.rs new file mode 100644 index 0000000..da3ee70 --- /dev/null +++ b/src/routes/message/service.rs @@ -0,0 +1,21 @@ +use super::domain::Message; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Message) -> Message { + todo!() +} + +pub async fn update(_id: u64, _item: Message) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..80dfbb2 --- /dev/null +++ b/src/routes/mod.rs @@ -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()) +} diff --git a/src/routes/server/domain.rs b/src/routes/server/domain.rs new file mode 100644 index 0000000..bb0b292 --- /dev/null +++ b/src/routes/server/domain.rs @@ -0,0 +1 @@ +pub struct Server {} diff --git a/src/routes/server/dto.rs b/src/routes/server/dto.rs new file mode 100644 index 0000000..bfe1999 --- /dev/null +++ b/src/routes/server/dto.rs @@ -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 {} diff --git a/src/routes/server/handlers.rs b/src/routes/server/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/server/handlers.rs @@ -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 +} diff --git a/src/routes/server/mapper.rs b/src/routes/server/mapper.rs new file mode 100644 index 0000000..52a3ce5 --- /dev/null +++ b/src/routes/server/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::Server, dto::ServerResponse}; + +pub fn to_response(_item: Server) -> ServerResponse { + todo!() +} diff --git a/src/routes/server/mod.rs b/src/routes/server/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/server/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/server/routes.rs b/src/routes/server/routes.rs new file mode 100644 index 0000000..9d51ca9 --- /dev/null +++ b/src/routes/server/routes.rs @@ -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), + ) +} diff --git a/src/routes/server/service.rs b/src/routes/server/service.rs new file mode 100644 index 0000000..9686fc8 --- /dev/null +++ b/src/routes/server/service.rs @@ -0,0 +1,21 @@ +use super::domain::Server; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_item: Server) -> Server { + todo!() +} + +pub async fn update(_id: u64, _item: Server) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/routes/user/domain.rs b/src/routes/user/domain.rs new file mode 100644 index 0000000..e6ad9f0 --- /dev/null +++ b/src/routes/user/domain.rs @@ -0,0 +1 @@ +pub struct User {} diff --git a/src/routes/user/dto.rs b/src/routes/user/dto.rs new file mode 100644 index 0000000..9772cee --- /dev/null +++ b/src/routes/user/dto.rs @@ -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 {} diff --git a/src/routes/user/handlers.rs b/src/routes/user/handlers.rs new file mode 100644 index 0000000..dfb714a --- /dev/null +++ b/src/routes/user/handlers.rs @@ -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 +} diff --git a/src/routes/user/mapper.rs b/src/routes/user/mapper.rs new file mode 100644 index 0000000..62c6467 --- /dev/null +++ b/src/routes/user/mapper.rs @@ -0,0 +1,5 @@ +use super::{domain::User, dto::UserResponse}; + +pub fn to_response(_user: User) -> UserResponse { + todo!() +} diff --git a/src/routes/user/mod.rs b/src/routes/user/mod.rs new file mode 100644 index 0000000..9c36fc8 --- /dev/null +++ b/src/routes/user/mod.rs @@ -0,0 +1,6 @@ +pub mod domain; +pub mod dto; +pub mod handlers; +pub mod mapper; +pub mod routes; +pub mod service; diff --git a/src/routes/user/routes.rs b/src/routes/user/routes.rs new file mode 100644 index 0000000..68bb761 --- /dev/null +++ b/src/routes/user/routes.rs @@ -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), + ) +} diff --git a/src/routes/user/service.rs b/src/routes/user/service.rs new file mode 100644 index 0000000..8ac5420 --- /dev/null +++ b/src/routes/user/service.rs @@ -0,0 +1,21 @@ +use super::domain::User; + +pub async fn find_all() -> Vec { + todo!() +} + +pub async fn find_by_id(_id: u64) -> Option { + todo!() +} + +pub async fn create(_user: User) -> User { + todo!() +} + +pub async fn update(_id: u64, _user: User) -> Option { + todo!() +} + +pub async fn delete(_id: u64) -> bool { + todo!() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..c72e4b9 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod password; diff --git a/src/utils/password.rs b/src/utils/password.rs new file mode 100644 index 0000000..5cc9125 --- /dev/null +++ b/src/utils/password.rs @@ -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 { + 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 { + let parsed_hash = PasswordHash::new(hash)?; + let argon2 = Argon2::default(); + + Ok(argon2 + .verify_password(password.as_bytes(), &parsed_hash) + .is_ok()) +} diff --git a/test b/test new file mode 100644 index 0000000..cd7eb47 --- /dev/null +++ b/test @@ -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. +L’inté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 +struct Worker { + id: usize, + sender: Sender, +} +Event Loop + +Boucle infinie : + +loop { + let event = rx.recv().unwrap(); + event.execute(); +} +Event Trait +trait ExecutableEvent { + fn execute(self: Box); +} +PHASE 2 — Système de Signaux +Objectif + +Créer : + +Signal, +connect(), +emit(), +subscribers, +callbacks dynamiques. +A implémenter +Signal générique +struct Signal { + subscribers: Vec>, +} +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; +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 + +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 s’exécuter sur le worker cible, +éviter les accès cross-thread directs. +PHASE 6 — Weak References +Objectif + +Reproduire l’auto-cleanup de Qt. + +A implémenter + +Utiliser : + +Arc +Weak +Comportement + +Si le receiver est détruit : + +la connexion devient invalide, +le runtime l’ignore automatiquement. +PHASE 7 — Event Bus Typé +Objectif + +Permettre : + +runtime.emit(EventType) +runtime.subscribe::() +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 s’abonner à 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, +} + +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. \ No newline at end of file