diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index fb24d27..009bd31 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -6,62 +6,62 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,7 +69,19 @@
+
+
+
+
+
+
@@ -91,7 +103,7 @@
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.rust.reset.selective.auto.import": "true",
- "git-widget-placeholder": "master",
+ "git-widget-placeholder": "v2",
"ignore.virus.scanning.warn.message": "true",
"junie.onboarding.icon.badge.shown": "true",
"last_opened_file_path": "//wsl.localhost/Debian/home/Nell/linux_dev/ox_speak_server",
@@ -103,11 +115,17 @@
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "",
"org.rust.first.attach.projects": "true",
- "settings.editor.selected.configurable": "preferences.pluginManager",
+ "settings.editor.selected.configurable": "ml.llm.LLMConfigurable",
"to.speed.mode.migration.done": "true",
"vue.rearranger.settings.migration": "true"
}
}
+
+
+
+
+
+
@@ -157,12 +175,31 @@
1755963473950
+
+
+
+
+
+
+
+
+ 1756218076891
+
+
+
+ 1756218076891
+
+
+
+
+
+
diff --git a/Cargo.lock b/Cargo.lock
index 2597c98..c290852 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,18 +17,38 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
-
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -38,12 +58,101 @@ dependencies = [
"libc",
]
+[[package]]
+name = "anstream"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
+dependencies = [
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.60.2",
+]
+
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
[[package]]
name = "atoi"
version = "2.0.0"
@@ -53,6 +162,12 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -66,7 +181,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [
"axum-core",
- "base64",
"bytes",
"form_urlencoded",
"futures-util",
@@ -86,10 +200,8 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
- "sha1",
"sync_wrapper",
"tokio",
- "tokio-tungstenite",
"tower",
"tower-layer",
"tower-service",
@@ -143,6 +255,20 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
+[[package]]
+name = "bigdecimal"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013"
+dependencies = [
+ "autocfg",
+ "libm",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "2.9.1"
@@ -152,6 +278,18 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -161,12 +299,57 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "borsh"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+[[package]]
+name = "bytecheck"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
+dependencies = [
+ "bytecheck_derive",
+ "ptr_meta",
+ "simdutf8",
+]
+
+[[package]]
+name = "bytecheck_derive"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -181,10 +364,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
-version = "1.2.30"
+version = "1.2.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
+checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
dependencies = [
+ "find-msvc-tools",
"shlex",
]
@@ -195,20 +379,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
-name = "chrono"
-version = "0.4.41"
+name = "cfg_aliases"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
- "android-tzdata",
"iana-time-zone",
- "js-sys",
"num-traits",
"serde",
- "wasm-bindgen",
"windows-link",
]
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@@ -279,26 +472,6 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "dashmap"
-version = "6.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
- "hashbrown 0.14.5",
- "lock_api",
- "once_cell",
- "parking_lot_core",
-]
-
-[[package]]
-name = "data-encoding"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
-
[[package]]
name = "der"
version = "0.7.10"
@@ -310,6 +483,16 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "deranged"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
+dependencies = [
+ "powerfmt",
+ "serde_core",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -330,7 +513,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
@@ -348,6 +531,38 @@ dependencies = [
"serde",
]
+[[package]]
+name = "env_filter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "envy"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -367,15 +582,21 @@ dependencies = [
[[package]]
name = "event-listener"
-version = "5.4.0"
+version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
+
[[package]]
name = "flume"
version = "0.11.1"
@@ -401,13 +622,19 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -498,19 +725,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi",
- "wasi 0.14.2+wasi-0.2.4",
+ "wasi",
]
[[package]]
@@ -521,30 +736,45 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
-version = "0.14.5"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
[[package]]
name = "hashbrown"
-version = "0.15.4"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
+[[package]]
+name = "hashbrown"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
- "hashbrown 0.15.4",
+ "hashbrown 0.15.5",
]
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
[[package]]
name = "heck"
version = "0.5.0"
@@ -632,28 +862,30 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
dependencies = [
+ "atomic-waker",
"bytes",
"futures-channel",
- "futures-util",
+ "futures-core",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
+ "pin-utils",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
+checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"bytes",
"futures-core",
@@ -667,9 +899,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.63"
+version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -777,9 +1009,9 @@ dependencies = [
[[package]]
name = "idna"
-version = "1.0.3"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
@@ -798,12 +1030,23 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.10.0"
+version = "2.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
- "hashbrown 0.15.4",
+ "hashbrown 0.16.0",
+]
+
+[[package]]
+name = "inherent"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
]
[[package]]
@@ -817,6 +1060,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
[[package]]
name = "itoa"
version = "1.0.15"
@@ -824,23 +1073,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
-name = "js-sys"
-version = "0.3.77"
+name = "jiff"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
- "once_cell",
- "wasm-bindgen",
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde",
]
[[package]]
-name = "kanal"
-version = "0.1.1"
+name = "jiff-static"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e3953adf0cd667798b396c2fa13552d6d9b3269d7dd1154c4c416442d1ff574"
+checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
- "futures-core",
- "lock_api",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
]
[[package]]
@@ -864,6 +1127,17 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+[[package]]
+name = "libredox"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
+dependencies = [
+ "bitflags",
+ "libc",
+ "redox_syscall",
+]
+
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
@@ -893,9 +1167,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.27"
+version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "matchit"
@@ -941,10 +1215,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"windows-sys 0.59.0",
]
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@@ -957,11 +1241,17 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
- "rand 0.8.5",
+ "rand",
"smallvec",
"zeroize",
]
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
[[package]]
name = "num-integer"
version = "0.1.46"
@@ -1007,26 +1297,64 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.104",
+]
+
[[package]]
name = "ox_speak_server"
version = "0.1.0"
dependencies = [
"arc-swap",
"axum",
- "bytes",
- "chrono",
- "crossbeam-utils",
- "dashmap",
"dotenvy",
- "event-listener",
- "kanal",
+ "env_logger",
+ "envy",
+ "hyper",
+ "log",
"parking_lot",
+ "sea-orm",
"serde",
"serde_json",
- "sqlx",
- "strum",
+ "socket2",
"tokio",
- "uuid",
+ "tower",
+ "tower-http",
]
[[package]]
@@ -1069,9 +1397,18 @@ dependencies = [
[[package]]
name = "percent-encoding"
-version = "2.3.1"
+version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pgvector"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b"
+dependencies = [
+ "serde",
+]
[[package]]
name = "pin-project-lite"
@@ -1113,14 +1450,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
-name = "potential_utf"
-version = "0.1.2"
+name = "portable-atomic"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
dependencies = [
"zerovec",
]
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -1130,6 +1488,37 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -1139,6 +1528,39 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+ "version_check",
+ "yansi",
+]
+
+[[package]]
+name = "ptr_meta"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
+dependencies = [
+ "ptr_meta_derive",
+]
+
+[[package]]
+name = "ptr_meta_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "quote"
version = "1.0.40"
@@ -1149,10 +1571,10 @@ dependencies = [
]
[[package]]
-name = "r-efi"
-version = "5.3.0"
+name = "radium"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
@@ -1161,18 +1583,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
-dependencies = [
- "rand_chacha 0.9.0",
- "rand_core 0.9.3",
+ "rand_chacha",
+ "rand_core",
]
[[package]]
@@ -1182,17 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core 0.6.4",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.9.3",
+ "rand_core",
]
[[package]]
@@ -1201,39 +1603,83 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom 0.2.16",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
-dependencies = [
- "getrandom 0.3.3",
+ "getrandom",
]
[[package]]
name = "redox_syscall"
-version = "0.5.15"
+version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
+checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags",
]
[[package]]
-name = "ring"
-version = "0.17.14"
+name = "regex"
+version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
- "cc",
- "cfg-if",
- "getrandom 0.2.16",
- "libc",
- "untrusted",
- "windows-sys 0.52.0",
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rend"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
+dependencies = [
+ "bytecheck",
+]
+
+[[package]]
+name = "rkyv"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
+dependencies = [
+ "bitvec",
+ "bytecheck",
+ "bytes",
+ "hashbrown 0.12.3",
+ "ptr_meta",
+ "rend",
+ "rkyv_derive",
+ "seahash",
+ "tinyvec",
+ "uuid",
+]
+
+[[package]]
+name = "rkyv_derive"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
]
[[package]]
@@ -1249,58 +1695,40 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
- "rand_core 0.6.4",
+ "rand_core",
"signature",
"spki",
"subtle",
"zeroize",
]
+[[package]]
+name = "rust_decimal"
+version = "1.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0"
+dependencies = [
+ "arrayvec",
+ "borsh",
+ "bytes",
+ "num-traits",
+ "rand",
+ "rkyv",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
-[[package]]
-name = "rustls"
-version = "0.23.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
-dependencies = [
- "once_cell",
- "ring",
- "rustls-pki-types",
- "rustls-webpki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls-pki-types"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
-dependencies = [
- "zeroize",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.103.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
-]
-
[[package]]
name = "rustversion"
-version = "1.0.21"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
@@ -1315,45 +1743,151 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
-name = "serde"
-version = "1.0.219"
+name = "sea-bae"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "sea-orm"
+version = "1.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335d87ec8e5c6eb4b2afb866dc53ed57a5cba314af63ce288db83047aa0fed4d"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "bigdecimal",
+ "chrono",
+ "futures-util",
+ "log",
+ "ouroboros",
+ "pgvector",
+ "rust_decimal",
+ "sea-orm-macros",
+ "sea-query",
+ "sea-query-binder",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "strum",
+ "thiserror",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "sea-orm-macros"
+version = "1.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68de7a2258410fd5e6ba319a4fe6c4af7811507fc714bbd76534ae6caa60f95f"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "sea-bae",
+ "syn 2.0.104",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sea-query"
+version = "0.32.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "inherent",
+ "ordered-float",
+ "rust_decimal",
+ "serde_json",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "sea-query-binder"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "rust_decimal",
+ "sea-query",
+ "serde_json",
+ "sqlx",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
name = "serde_json"
-version = "1.0.141"
+version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
+ "serde_core",
]
[[package]]
name = "serde_path_to_error"
-version = "0.1.17"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
+ "serde_core",
]
[[package]]
@@ -1412,9 +1946,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
- "rand_core 0.6.4",
+ "rand_core",
]
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
[[package]]
name = "slab"
version = "0.4.10"
@@ -1479,6 +2019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
"base64",
+ "bigdecimal",
"bytes",
"chrono",
"crc",
@@ -1489,25 +2030,25 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
- "hashbrown 0.15.4",
+ "hashbrown 0.15.5",
"hashlink",
"indexmap",
"log",
"memchr",
"once_cell",
"percent-encoding",
- "rustls",
+ "rust_decimal",
"serde",
"serde_json",
"sha2",
"smallvec",
"thiserror",
+ "time",
"tokio",
"tokio-stream",
"tracing",
"url",
"uuid",
- "webpki-roots 0.26.11",
]
[[package]]
@@ -1520,7 +2061,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
- "syn",
+ "syn 2.0.104",
]
[[package]]
@@ -1531,7 +2072,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
- "heck",
+ "heck 0.5.0",
"hex",
"once_cell",
"proc-macro2",
@@ -1543,7 +2084,7 @@ dependencies = [
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
- "syn",
+ "syn 2.0.104",
"tokio",
"url",
]
@@ -1556,6 +2097,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
+ "bigdecimal",
"bitflags",
"byteorder",
"bytes",
@@ -1578,8 +2120,9 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
- "rand 0.8.5",
+ "rand",
"rsa",
+ "rust_decimal",
"serde",
"sha1",
"sha2",
@@ -1587,6 +2130,7 @@ dependencies = [
"sqlx-core",
"stringprep",
"thiserror",
+ "time",
"tracing",
"uuid",
"whoami",
@@ -1600,6 +2144,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
+ "bigdecimal",
"bitflags",
"byteorder",
"chrono",
@@ -1617,8 +2162,10 @@ dependencies = [
"log",
"md-5",
"memchr",
+ "num-bigint",
"once_cell",
- "rand 0.8.5",
+ "rand",
+ "rust_decimal",
"serde",
"serde_json",
"sha2",
@@ -1626,6 +2173,7 @@ dependencies = [
"sqlx-core",
"stringprep",
"thiserror",
+ "time",
"tracing",
"uuid",
"whoami",
@@ -1652,6 +2200,7 @@ dependencies = [
"serde_urlencoded",
"sqlx-core",
"thiserror",
+ "time",
"tracing",
"url",
"uuid",
@@ -1663,6 +2212,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "stringprep"
version = "0.1.5"
@@ -1676,24 +2231,9 @@ dependencies = [
[[package]]
name = "strum"
-version = "0.27.2"
+version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
-dependencies = [
- "strum_macros",
-]
-
-[[package]]
-name = "strum_macros"
-version = "0.27.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
[[package]]
name = "subtle"
@@ -1701,6 +2241,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "syn"
version = "2.0.104"
@@ -1726,27 +2277,64 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
-name = "thiserror"
-version = "2.0.12"
+name = "tap"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
+name = "thiserror"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.12"
+version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "time"
+version = "0.3.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+
+[[package]]
+name = "time-macros"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
+dependencies = [
+ "num-conv",
+ "time-core",
]
[[package]]
@@ -1761,9 +2349,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.9.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
+checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
@@ -1802,7 +2390,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
@@ -1817,15 +2405,33 @@ dependencies = [
]
[[package]]
-name = "tokio-tungstenite"
-version = "0.26.2"
+name = "toml_datetime"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
+checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
dependencies = [
- "futures-util",
- "log",
- "tokio",
- "tungstenite",
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
+dependencies = [
+ "winnow",
]
[[package]]
@@ -1844,6 +2450,20 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "tower-http"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "http",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+]
+
[[package]]
name = "tower-layer"
version = "0.3.3"
@@ -1876,7 +2496,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
@@ -1888,23 +2508,6 @@ dependencies = [
"once_cell",
]
-[[package]]
-name = "tungstenite"
-version = "0.26.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
-dependencies = [
- "bytes",
- "data-encoding",
- "http",
- "httparse",
- "log",
- "rand 0.9.2",
- "sha1",
- "thiserror",
- "utf-8",
-]
-
[[package]]
name = "typenum"
version = "1.18.0"
@@ -1938,29 +2541,18 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
-[[package]]
-name = "untrusted"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
-
[[package]]
name = "url"
-version = "2.5.4"
+version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
+ "serde",
]
-[[package]]
-name = "utf-8"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -1968,12 +2560,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
-name = "uuid"
-version = "1.18.0"
+name = "utf8parse"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
- "getrandom 0.3.3",
"js-sys",
"serde",
"wasm-bindgen",
@@ -1997,15 +2594,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-[[package]]
-name = "wasi"
-version = "0.14.2+wasi-0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
-dependencies = [
- "wit-bindgen-rt",
-]
-
[[package]]
name = "wasite"
version = "0.1.0"
@@ -2014,35 +2602,36 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
-version = "0.2.100"
+version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
+ "wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.100"
+version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.100"
+version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2050,59 +2639,41 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.100"
+version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.100"
+version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
dependencies = [
"unicode-ident",
]
-[[package]]
-name = "webpki-roots"
-version = "0.26.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
-dependencies = [
- "webpki-roots 1.0.2",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
-dependencies = [
- "rustls-pki-types",
-]
-
[[package]]
name = "whoami"
-version = "1.6.0"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
+checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
dependencies = [
- "redox_syscall",
+ "libredox",
"wasite",
]
[[package]]
name = "windows-core"
-version = "0.61.2"
+version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
dependencies = [
"windows-implement",
"windows-interface",
@@ -2113,46 +2684,46 @@ dependencies = [
[[package]]
name = "windows-implement"
-version = "0.60.0"
+version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
name = "windows-interface"
-version = "0.59.1"
+version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
name = "windows-link"
-version = "0.1.3"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-result"
-version = "0.3.4"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
-version = "0.4.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
dependencies = [
"windows-link",
]
@@ -2168,20 +2739,20 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.52.0"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
-version = "0.59.0"
+version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
- "windows-targets 0.52.6",
+ "windows-targets 0.53.4",
]
[[package]]
@@ -2208,13 +2779,30 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm",
+ "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
+[[package]]
+name = "windows-targets"
+version = "0.53.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -2227,6 +2815,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -2239,6 +2833,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -2251,12 +2851,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -2269,6 +2881,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -2281,6 +2899,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -2293,6 +2917,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@@ -2306,12 +2936,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
-name = "wit-bindgen-rt"
-version = "0.39.0"
+name = "windows_x86_64_msvc"
+version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "winnow"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
- "bitflags",
+ "memchr",
]
[[package]]
@@ -2320,6 +2956,21 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
[[package]]
name = "yoke"
version = "0.8.0"
@@ -2340,28 +2991,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.26"
+version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.26"
+version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
@@ -2381,7 +3032,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"synstructure",
]
@@ -2404,9 +3055,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.2"
+version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [
"yoke",
"zerofrom",
@@ -2421,5 +3072,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
diff --git a/Cargo.toml b/Cargo.toml
index 641630c..f2990cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,19 +14,19 @@ crate-type = ["staticlib", "cdylib", "rlib"]
debug = true
[dependencies]
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-parking_lot = "0.12"
+log = "0.4"
+env_logger = "0.11"
tokio = { version = "1.47", features = ["full"] }
-strum = {version = "0.27", features = ["derive"] }
-uuid = {version = "1.18", features = ["v4", "serde"] }
-event-listener = "5.4"
-dashmap = "6.1"
-bytes = "1.10"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
arc-swap = "1.7"
-crossbeam-utils = "0.8"
-kanal = "0.1"
-axum = { version = "0.8", features = ["ws", "default"] }
-chrono = {version = "0.4", features = ["serde"]}
-sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "chrono", "uuid", "any", "sqlite", "postgres", "mysql" ] }
-dotenvy = "0.15"
\ No newline at end of file
+dotenvy = "0.15"
+envy = "0.4"
+socket2 = "0.6"
+parking_lot = "0.12"
+axum = "0.8"
+tower = "0.5"
+tower-http = "0.6"
+hyper = "1.7"
+
+sea-orm = { version = "1.1", features = [ "sqlx-sqlite", "runtime-tokio", "macros" ] }
\ No newline at end of file
diff --git a/README.md b/README.md
index 9b2a3a1..e4fd812 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,15 @@
+L'idée est d'avoir une séparation avec une gestion à la "Qt". En gros, avoir des composants "indépendants" qui connaisse leur propre état
+et mettre à dispo une succession de méthodes pour interagir avec ces composants dans "App"
+
+## Modules :
+- App : Execution générale et manipulation des autres composant en "haut niveau", interconnexion entre les app
+- Network :
+ - Http : API
+ - WebSocket : Un server websocket avec ses clients (pour la gestion haut niveau)
+ - UDP : Un server UDP avec ses clients (pour la gestion haut niveau)
+
créer un fichier de migration sqlx :
```shell
sqlx migrate add --source src/store/migrations migration_name
```
-# Example
\ No newline at end of file
diff --git a/src/app/app.rs b/src/app/app.rs
index 21e1892..e7b5203 100644
--- a/src/app/app.rs
+++ b/src/app/app.rs
@@ -1,80 +1,41 @@
-use std::time::Duration;
-use tokio::sync::mpsc;
-use tokio::time::interval;
-use crate::domain::client::ClientManager;
-use crate::domain::event::{Event, EventBus};
-use crate::network::http::HttpServer;
-use crate::network::udp::UdpServer;
-use crate::runtime::dispatcher::Dispatcher;
-use crate::store::store_service::StoreService;
+use std::io;
+use std::sync::Arc;
+use std::net::SocketAddr;
+use crate::network::udp::server::UdpServer;
+use crate::utils::config::Config;
+use crate::utils::logger::ContextLogger;
pub struct App {
- // Communication inter-components
- event_bus: EventBus,
- dispatcher: Dispatcher,
- event_rx: kanal::AsyncReceiver,
+ config: Config,
+ context: Arc,
- // Network
- udp_server: UdpServer,
- http_server: HttpServer,
-
- // Clients
- client_manager: ClientManager,
-
- // store
- store: StoreService,
+ logger: ContextLogger,
}
impl App {
- pub async fn new() -> Self {
- let (event_bus, event_rx) = EventBus::new();
+ pub async fn new(config: Config) -> io::Result {
+ let logger = ContextLogger::new("APP");
- let store = StoreService::new("./db.sqlite").await.unwrap();
-
- let udp_server = UdpServer::new(event_bus.clone(), "0.0.0.0:5000").await;
- let http_server = HttpServer::new(event_bus.clone(), store.clone());
- let client_manager = ClientManager::new();
-
- let dispatcher = Dispatcher::new(event_bus.clone(), udp_server.clone(), client_manager.clone(), store.clone()).await;
-
-
- Self {
- event_bus,
- dispatcher,
- event_rx,
- udp_server,
- http_server,
- client_manager,
- store
- }
- }
-
- pub async fn start(&mut self) {
- for i in 0..4 {
- let dispatcher = self.dispatcher.clone();
- let event_rx = self.event_rx.clone();
- tokio::spawn(async move {
- dispatcher.start(event_rx).await;
- });
- }
-
-
- let _ = self.udp_server.start().await;
- let _ = self.http_server.start("0.0.0.0:5000").await;
- let _ = self.tick_tasks().await;
- println!("App started");
- }
-
- async fn tick_tasks(&self) {
- let event_bus = self.event_bus.clone();
- tokio::spawn(async move {
- let mut interval = interval(Duration::from_secs(1));
- loop {
- // println!("Tick");
- interval.tick().await;
- let _ = event_bus.emit(Event::TickSeconds).await;
+ // initialise context
+ let udp_server = match UdpServer::new("127.0.0.1:8080") {
+ Ok(udp_server) => udp_server,
+ Err(e) => {
+ return Err(io::Error::new(io::ErrorKind::Other, format!("Failed to create UDP server: {}", e)))
}
- });
- }
+ };
+ let context = Context {
+ udp_server
+ };
+
+ Ok(Self {
+ config,
+ context: Arc::new(context),
+ logger
+ })
+ }
+}
+
+struct Context {
+ udp_server: UdpServer
}
\ No newline at end of file
diff --git a/src/app/conf.rs b/src/app/conf.rs
deleted file mode 100644
index ecc9c46..0000000
--- a/src/app/conf.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::env;
-use std::path::Path;
-
-pub fn load_env() {
- // display le répertoire de travail
- match env::current_dir() {
- Ok(path) => {
- println!("Répertoire de travail: {}", path.display())
- }
- Err(e) => {
- eprintln!("Erreur pour obtenir le répertoire: {}", e)
- }
- }
-
- // Vérifier si le .env existe
- let env_path = Path::new(".env");
- if env_path.exists() {
- println!(".env trouvé");
-
- // Charger le .env
- match dotenvy::from_path(env_path) {
- Ok(_) => println!("✅ Fichier .env chargé avec succès"),
- Err(e) => eprintln!("❌ Erreur lors du chargement du .env: {}", e),
- }
- } else {
- println!("⚠️ Le fichier .env n'existe pas");
- }
-}
\ No newline at end of file
diff --git a/src/app/http/api/core.rs b/src/app/http/api/core.rs
new file mode 100644
index 0000000..3303277
--- /dev/null
+++ b/src/app/http/api/core.rs
@@ -0,0 +1,28 @@
+use axum::http::StatusCode;
+use axum::{Json, Router};
+use axum::routing::get;
+use serde_json::json;
+
+pub fn routes() -> Router {
+ Router::new()
+ .route("/", get(root))
+ .route("/health", get(health))
+ .route("/status", get(status))
+}
+
+async fn root() -> &'static str {
+ "OX Speak Server - HTTP API"
+}
+
+async fn health() -> StatusCode {
+ StatusCode::OK
+}
+
+async fn status() -> Json {
+ Json(json!({
+ "status": "ok",
+ "service": "ox-speak-server",
+ "version": env!("CARGO_PKG_VERSION"),
+ "workers": tokio::runtime::Handle::current().metrics().num_workers()
+ }))
+}
\ No newline at end of file
diff --git a/src/app/http/api/message.rs b/src/app/http/api/message.rs
new file mode 100644
index 0000000..12e96ef
--- /dev/null
+++ b/src/app/http/api/message.rs
@@ -0,0 +1,6 @@
+use axum::Router;
+
+pub fn routes() -> Router {
+ Router::new()
+ .nest("/message", Router::new())
+}
\ No newline at end of file
diff --git a/src/app/http/api/mod.rs b/src/app/http/api/mod.rs
new file mode 100644
index 0000000..acfc57a
--- /dev/null
+++ b/src/app/http/api/mod.rs
@@ -0,0 +1,3 @@
+pub mod core;
+pub mod message;
+pub mod server;
\ No newline at end of file
diff --git a/src/core/mod.rs b/src/app/http/api/server.rs
similarity index 100%
rename from src/core/mod.rs
rename to src/app/http/api/server.rs
diff --git a/src/app/http/router.rs b/src/app/http/router.rs
new file mode 100644
index 0000000..8a55e5c
--- /dev/null
+++ b/src/app/http/router.rs
@@ -0,0 +1,13 @@
+use axum::Router;
+use crate::app::http::api;
+use crate::app::http::api::core;
+
+pub fn configure_routes() -> Router {
+ let api_router = Router::new();
+
+
+ Router::new()
+ .merge(core::routes())
+ .nest("/api", api_router )
+
+}
diff --git a/src/domain/client.rs b/src/domain/client.rs
deleted file mode 100644
index 599700f..0000000
--- a/src/domain/client.rs
+++ /dev/null
@@ -1,264 +0,0 @@
-
-//! Gestion des clients pour les connexions UDP
-//!
-//! Ce module fournit les structures et méthodes pour gérer les clients
-//! connectés au serveur UDP, incluant leur tracking et leurs modifications.
-
-use std::collections::HashSet;
-use std::net::SocketAddr;
-use std::sync::Arc;
-use tokio::time::Instant;
-use uuid::Uuid;
-use std::hash::{Hash, Hasher};
-use std::time::Duration;
-use crossbeam_utils::atomic::AtomicCell;
-use crate::utils::shared_store::SharedArcMap;
-
-/// Représente un client connecté au serveur UDP
-///
-/// Chaque client est identifié par un UUID unique et contient
-/// son adresse réseau ainsi que l'heure de sa dernière activité.
-/// Le `last_seen` utilise AtomicCell pour des mises à jour lock-free.
-#[derive(Debug)]
-pub struct Client {
- id: Uuid,
- address: SocketAddr,
- last_seen: AtomicCell,
-}
-
-/// Gestionnaire threadsafe pour les clients connectés
-///
-/// Utilise `SharedArcMap` pour permettre un accès concurrent sécurisé
-/// aux clients depuis plusieurs threads avec des performances optimisées
-/// pour les lectures fréquentes. Les clients sont stockés dans des Arc
-/// pour éviter les clones coûteux.
-#[derive(Clone)]
-pub struct ClientManager {
- udp_clients: SharedArcMap,
-}
-
-impl Client {
- /// Crée un nouveau client avec un UUID généré automatiquement
- pub fn new(address: SocketAddr) -> Self {
- let id = Uuid::new_v4();
- Self {
- id,
- address,
- last_seen: AtomicCell::new(Instant::now()),
- }
- }
-
- /// Retourne le UUID unique du client
- pub fn id(&self) -> Uuid {
- self.id
- }
-
- /// Retourne l'adresse socket du client
- pub fn address(&self) -> SocketAddr {
- self.address
- }
-
- /// Retourne l'instant de la dernière activité du client
- /// Accès lock-free grâce à AtomicCell
- pub fn last_seen(&self) -> Instant {
- self.last_seen.load()
- }
-
- /// Met à jour l'heure de dernière activité du client à maintenant
- /// Opération lock-free grâce à AtomicCell
- pub fn update_last_seen(&self) {
- self.last_seen.store(Instant::now());
- }
-}
-
-impl Hash for Client {
- fn hash(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
-
-impl PartialEq for Client {
- fn eq(&self, other: &Self) -> bool {
- self.id == other.id
- }
-}
-
-impl Eq for Client {}
-
-impl ClientManager {
- /// Crée un nouveau gestionnaire de clients vide
- pub fn new() -> Self {
- Self {
- udp_clients: SharedArcMap::new(),
- }
- }
-
- /// Crée un nouveau gestionnaire de clients avec une capacité initiale
- pub fn with_capacity(capacity: usize) -> Self {
- Self {
- udp_clients: SharedArcMap::with_capacity(capacity),
- }
- }
-
- /// Ajoute un client au gestionnaire
- /// Retourne l'ancien client Arc s'il existait déjà
- pub fn add(&self, client: Client) -> Option> {
- self.udp_clients.insert(client.address(), client)
- }
-
- /// Supprime un client du gestionnaire par son adresse
- /// Retourne l'Arc du client supprimé
- pub fn remove(&self, address: SocketAddr) -> Option> {
- self.udp_clients.remove(&address)
- }
-
- /// Supprime un client du gestionnaire par son instance
- /// Retourne l'Arc du client supprimé
- pub fn remove_client(&self, client: &Client) -> Option> {
- self.udp_clients.remove(&client.address())
- }
-
- /// Vérifie si un client existe pour une adresse donnée
- pub fn client_exists(&self, address: SocketAddr) -> bool {
- self.udp_clients.contains_key(&address)
- }
-
- /// Récupère un Arc vers le client par son adresse
- /// Très efficace - pas de clone du Client
- pub fn get_client_by_address(&self, address: SocketAddr) -> Option> {
- self.udp_clients.get(&address)
- }
-
- pub fn get_uuid_by_address(&self, address: SocketAddr) -> Option {
- self.udp_clients.get(&address).map(|client| client.id)
- }
-
- /// Récupère toutes les adresses des clients connectés
- pub fn get_all_addresses(&self) -> HashSet {
- self.udp_clients.keys().collect()
- }
-
- /// Retourne le nombre de clients connectés
- pub fn len(&self) -> usize {
- self.udp_clients.len()
- }
-
- /// Vérifie si le gestionnaire est vide
- pub fn is_empty(&self) -> bool {
- self.udp_clients.is_empty()
- }
-
- /// Met à jour l'heure de dernière activité d'un client
- /// Utilise la méthode modify de SharedArcMap pour une opération lock-free
- pub fn update_client_last_seen(&self, address: SocketAddr) -> bool {
- self.udp_clients.modify(&address, |client| {
- client.update_last_seen();
- })
- }
-
- /// Supprime les clients trop vieux
- /// Utilise l'accès lock-free pour identifier les clients à supprimer
- pub fn cleanup(&self, max_age: Duration) {
- let now = Instant::now();
- let addresses_to_remove: Vec = self
- .udp_clients
- .read()
- .iter()
- .filter(|(_, client)| now - client.last_seen() >= max_age)
- .map(|(addr, _)| *addr)
- .collect();
-
- for address in addresses_to_remove {
- self.udp_clients.remove(&address);
- }
- }
-
- /// Modifie un client via une closure
- /// Utilise la méthode modify optimisée de SharedArcMap
- ///
- /// # Arguments
- /// * `address` - L'adresse du client à modifier
- /// * `f` - La closure qui recevra une référence vers le client
- ///
- /// # Returns
- /// `true` si le client a été trouvé et modifié, `false` sinon
- ///
- /// # Examples
- /// ```ignore
- /// let client_manager = ClientManager::new();
- /// let addr = "127.0.0.1:8080".parse().unwrap();
- ///
- /// // Mise à jour de last_seen (lock-free)
- /// client_manager.modify_client(addr, |client| {
- /// client.update_last_seen();
- /// });
- ///
- /// // Accès aux propriétés du client
- /// let found = client_manager.modify_client(addr, |client| {
- /// println!("Client ID: {}", client.id());
- /// println!("Dernière activité: {:?}", client.last_seen());
- /// });
- /// ```
- pub fn modify_client(&self, address: SocketAddr, f: F) -> bool
- where
- F: FnOnce(&Client),
- {
- self.udp_clients.modify(&address, f)
- }
-
- /// Obtient une référence en lecture seule vers tous les clients
- /// Accès lock-free ultra-rapide
- pub fn read_all(&self) -> Arc>> {
- self.udp_clients.read()
- }
-
- /// Vide tous les clients
- pub fn clear(&self) {
- self.udp_clients.clear();
- }
-
- /// Itère sur tous les clients avec leurs Arc
- /// Très efficace - pas de clone des clients
- pub fn iter(&self) -> impl Iterator- )> {
- self.udp_clients.iter()
- }
-
- /// Itère sur toutes les adresses des clients
- pub fn addresses(&self) -> impl Iterator
- {
- self.udp_clients.keys()
- }
-
- /// Itère sur tous les Arc des clients
- pub fn clients(&self) -> impl Iterator
- > {
- self.udp_clients.values()
- }
-
- /// Compte le nombre de clients actifs dans une durée donnée
- /// Utilise l'accès lock-free pour une performance optimale
- pub fn count_active_clients(&self, max_age: Duration) -> usize {
- let now = Instant::now();
- self.udp_clients
- .read()
- .iter()
- .filter(|(_, client)| now - client.last_seen() < max_age)
- .count()
- }
-
- /// Obtient les adresses des clients actifs
- /// Très efficace grâce à l'accès lock-free
- pub fn get_active_addresses(&self, max_age: Duration) -> Vec {
- let now = Instant::now();
- self.udp_clients
- .read()
- .iter()
- .filter(|(_, client)| now - client.last_seen() < max_age)
- .map(|(addr, _)| *addr)
- .collect()
- }
-}
-
-impl Default for ClientManager {
- fn default() -> Self {
- Self::new()
- }
-}
\ No newline at end of file
diff --git a/src/domain/event.rs b/src/domain/event.rs
deleted file mode 100644
index 1f0af95..0000000
--- a/src/domain/event.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use crate::network::protocol::{UDPMessage};
-
-#[derive(Clone, Debug)]
-pub enum Event {
- AppStarted,
- AppStopped,
-
- UdpStarted,
- UdpStopped,
- UdpIn(UDPMessage),
- UdpOut(UDPMessage),
-
- TickSeconds
-}
-
-#[derive(Clone)]
-pub struct EventBus {
- pub sender: kanal::AsyncSender,
-}
-
-impl EventBus {
- pub fn new() -> (Self, kanal::AsyncReceiver) {
- let (sender, receiver) = kanal::bounded_async::(4096);
- (Self { sender }, receiver)
- }
-
- pub async fn emit(&self, event: Event) {
- let _ = self.sender.send(event).await;
- }
-
- pub fn emit_sync(&self, event: Event) {
- let _ = self.sender.try_send(event);
- }
-
- pub fn clone_sender(&self) -> kanal::AsyncSender {
- self.sender.clone()
- }
-}
\ No newline at end of file
diff --git a/src/domain/mod.rs b/src/domain/mod.rs
deleted file mode 100644
index f4be7fc..0000000
--- a/src/domain/mod.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-pub mod event;
-pub mod user;
-pub mod client;
-pub mod models;
\ No newline at end of file
diff --git a/src/domain/models.rs b/src/domain/models.rs
deleted file mode 100644
index 7e359cd..0000000
--- a/src/domain/models.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use std::sync::Arc;
-use parking_lot::Mutex;
-use uuid::Uuid;
-use chrono::{DateTime, Utc};
-
-/////////////////////////////////////
-//////////// Main models ///////////
-////////////////////////////////////
-
-struct SubServer {
- // Un serveur (master) peu avoir plusieurs subserver
- // (le master, pas besoin de le représenter, sa configuration sera depuis un .env)
- id: Uuid,
- name: String,
- password: String, // voir si on le hash, mais sera certainement pas nécessaire.
- created_at: DateTime,
-}
-
-struct Channel {
- id: Uuid,
- channel_type: ChannelType, // le type ne pourra pas être edit, comme discord
-
- sub_server_id: Uuid, // fk subserver
- name: String,
- created_at: DateTime,
-}
-
-struct User {
- id: Uuid,
- name: String, // je sais pas si il est nécessaire étant donné qu'on utilisera display_name de la relation.
- pub_key: String, // l'identification se fera par clé public/privé, comme teamspeak
-}
-
-struct Message {
- id: Uuid,
- channel_id: Uuid,
- user_id: Uuid,
- content: String,
- created_at: DateTime,
-}
-
-
-/////////////////////////////////////
-////////// n-n relations ///////////
-////////////////////////////////////
-
-struct SubServerUser {
- sub_server: SubServer,
- user: User,
- display_name: String,
-}
-
-/////////////////////////////////////
-//////// Enum Type (choice) ////////
-////////////////////////////////////
-enum ChannelType {
- Text,
- Voice,
-}
-
-/// Store centralisé pour tous les modèles de données
-#[derive(Clone)]
-pub struct DataStore {
- sub_servers: Arc>
-}
\ No newline at end of file
diff --git a/src/domain/user.rs b/src/domain/user.rs
deleted file mode 100644
index bfcf8f1..0000000
--- a/src/domain/user.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::net::SocketAddr;
-use std::sync::Arc;
-use dashmap::DashMap;
-use uuid::Uuid;
-
-pub struct User {
- id: Uuid,
- udp_addr: Option,
-}
-
-#[derive(Clone)]
-pub struct UserManager {
- users: Arc>,
-}
-
-impl User {
- pub fn new(id: Uuid) -> Self {
- Self {
- id,
- udp_addr: None,
- }
- }
-
- pub fn default() -> Self {
- Self::new(Uuid::new_v4())
- }
-
- pub fn set_udp_addr(&mut self, udp_addr: SocketAddr) {
- self.udp_addr = Some(udp_addr);
- }
-}
-
-impl UserManager {
- pub fn new() -> Self {
- Self {
- users: Arc::new(DashMap::new())
- }
- }
-
- pub fn add_user(&self, user: User) {
- self.users.insert(user.id, user);
- }
-
- pub fn delete_user(&self, user: User) {
- self.users.remove(&user.id);
- }
-}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 9f631af..4703bfa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,5 @@
-pub mod app;
-pub mod core;
-pub mod domain;
-pub mod network;
-pub mod runtime;
+
pub mod utils;
+pub mod network;
+pub mod app;
pub mod store;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 3f95056..b5922e9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,29 @@
+use std::sync::Arc;
+use parking_lot::RwLock;
use tokio::signal;
+use ox_speak_server_lib::utils::config::Config;
use ox_speak_server_lib::app::app::App;
-use ox_speak_server_lib::app::conf::load_env;
+use ox_speak_server_lib::utils::logger::init_logger;
#[tokio::main]
async fn main() {
- // Charger le .env
- load_env();
- let mut app = App::new().await;
- app.start().await;
+ init_logger("debug");
+
+ // Charger le .env
+ let config = match Config::load() {
+ Ok(config) => config,
+ Err(err) => {
+ eprintln!("Failed to load configuration: {}", err);
+ return;
+ }
+ };
+
+ // Initialiser le logger
+ // init_logger(&config.log_level);
+
+ let app = App::new(config).await;
+ // app.start().await;
// Attendre le signal Ctrl+C
match signal::ctrl_c().await {
@@ -19,5 +34,4 @@ async fn main() {
eprintln!("Erreur lors de l'écoute du signal: {}", err);
}
}
-
}
diff --git a/src/network/http.rs b/src/network/http.rs
deleted file mode 100644
index 91b577e..0000000
--- a/src/network/http.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use std::net::SocketAddr;
-use std::sync::Arc;
-use axum::{extract::{ws::WebSocket, ws::WebSocketUpgrade, State}, response::Response, Json, Router, routing::{get, post}, middleware};
-use axum::body::Body;
-use axum::http::{HeaderValue, Request, StatusCode};
-use axum::middleware::Next;
-use tokio::net::TcpListener;
-use crate::domain::event::EventBus;
-use crate::network::http_routes::master;
-use crate::store::store_service::StoreService;
-
-#[derive(Clone)]
-pub struct HttpState {
- pub event_bus: EventBus,
- pub store: StoreService
-}
-
-#[derive(Clone)]
-pub struct HttpServer {
- state: HttpState
-}
-
-impl HttpServer {
- pub fn new(event_bus: EventBus, store: StoreService) -> Self {
- Self {
- state: HttpState {
- event_bus,
- store
- },
- }
- }
-
- pub async fn start(&self, addr: &str) -> Result<(), Box> {
- let router = self.create_router();
- let listener = TcpListener::bind(addr).await?;
-
- println!("HTTP/WebSocket server listening on addr {}", listener.local_addr()?);
- tokio::spawn(async move {
- let _ = axum::serve(listener, router).await;
- });
- Ok(())
-
- }
-
- fn create_router(&self) -> Router {
- let api_route = Router::new()
- .nest("/master", master::create_router())
- .layer(middleware::from_fn(app_only_middleware));
-
- Router::new()
- .nest("/api", api_route)
- // .route("/ws", get(Self::ws))
- // .route("/stats/", get(Self::stats))
- .with_state(self.state.clone())
- }
-}
-
-/// Routes
-impl HttpServer {
- async fn stats(State(state): State>) -> Json {
- todo!("a faire")
- }
-
- async fn ws(ws: WebSocketUpgrade, State(state): State>) -> Response {
- ws.on_upgrade(|socket| async move {
- todo!("a faire")
- })
- }
-}
-
-// Middlewares
-async fn app_only_middleware(request: Request, next: Next) -> Result {
- let headers = request.headers();
- let expected_client = HeaderValue::from_static("ox_speak");
- let expected_version = HeaderValue::from_static("1.0");
- let expected_user_agent = HeaderValue::from_static("OxSpeak/1.0");
-
- if headers.get("X-Client-Type") != Some(&expected_client) ||
- headers.get("X-Protocol-Version") != Some(&expected_version) ||
- headers.get("User-Agent") != Some(&expected_user_agent) {
- return Ok(Response::builder()
- .status(StatusCode::BAD_REQUEST)
- .body("Invalid client".into())
- .unwrap());
- }
-
- Ok(next.run(request).await)
-
-}
\ No newline at end of file
diff --git a/src/network/http/mod.rs b/src/network/http/mod.rs
new file mode 100644
index 0000000..9310323
--- /dev/null
+++ b/src/network/http/mod.rs
@@ -0,0 +1 @@
+mod server;
\ No newline at end of file
diff --git a/src/network/http/server.rs b/src/network/http/server.rs
new file mode 100644
index 0000000..83464cc
--- /dev/null
+++ b/src/network/http/server.rs
@@ -0,0 +1,53 @@
+use std::io;
+use std::net::SocketAddr;
+use axum::{Router, routing::get, http::StatusCode, Json};
+use tokio::net::TcpListener;
+use crate::utils::logger::ContextLogger;
+
+pub struct HttpServer {
+ bind_addr: SocketAddr,
+ logger: ContextLogger,
+}
+
+impl HttpServer {
+ pub fn new(bind_addr: &str) -> io::Result {
+ let bind_addr = bind_addr.parse::()
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
+
+ let logger = ContextLogger::new("HTTP");
+ logger.info(&format!("Creating HTTP server on {}", bind_addr));
+
+ Ok(Self { bind_addr, logger })
+ }
+
+ pub async fn run(self) -> io::Result<()> {
+ self.logger.info(&format!("Starting HTTP server on {}", self.bind_addr));
+
+ let listener = TcpListener::bind(self.bind_addr).await?;
+ let app = create_app(self.logger.clone());
+
+ axum::serve(listener, app)
+ .await
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
+ }
+}
+
+fn create_app(logger: ContextLogger) -> Router {
+ Router::new()
+ .route("/", get(|| async { "OX Speak Server - HTTP API" }))
+ .route("/health", get(|| async { StatusCode::OK }))
+ .route("/api/status", get(status_handler))
+ .with_state(logger)
+}
+
+async fn status_handler(
+ axum::extract::State(logger): axum::extract::State
+) -> Json {
+ logger.info("Status endpoint called");
+
+ Json(serde_json::json!({
+ "status": "ok",
+ "service": "ox-speak-server",
+ "workers": tokio::runtime::Handle::current().metrics().num_workers()
+ }))
+}
\ No newline at end of file
diff --git a/src/network/http_routes/channel.rs b/src/network/http_routes/channel.rs
deleted file mode 100644
index 5ce3d7b..0000000
--- a/src/network/http_routes/channel.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use axum::Router;
-use axum::routing::post;
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- // .route("/auth/", post(join_master_server))
-}
\ No newline at end of file
diff --git a/src/network/http_routes/master.rs b/src/network/http_routes/master.rs
deleted file mode 100644
index d72af99..0000000
--- a/src/network/http_routes/master.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::collections::{HashMap};
-use std::hash::Hash;
-use axum::{
- extract::{Path, State},
- response::Json,
- routing::{get, post, put, delete},
- Router,
-};
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- .route("/auth/", post(join))
-}
-
-pub async fn challenge(State(state): State) -> Json> {
- // permet de sécuriser les échanges entre le client et le serveur pour qu'elle soit propre à l'application
- // l'idée est d'éviter que les système comme les navigateurs, curl ... puisse accéder à l'application
- todo!("challenge master server")
-}
-
-pub async fn join(State(state): State) -> Json> {
- // Cette page est systématiquement appelé à la première connexion du client
- // dans le payload de la requête, on aura les info client
- // public key
- // dans la réponse, on aura le status
- // success, error, password_needed
-
- // match determine_server_state().await {
- // ServerState::FirstSetup => {
- // // Logique de création du super admin
- // // Validation des champs requis (password, username)
- // }
- // ServerState::RequiresPassword => {
- // // Logique d'authentification
- // // Validation du mot de passe serveur
- // }
- // ServerState::Ready => {
- // // Logique de connexion normale
- // // Juste la public key suffit
- // }
- // }
-
- todo!("join master server")
-
-}
\ No newline at end of file
diff --git a/src/network/http_routes/message.rs b/src/network/http_routes/message.rs
deleted file mode 100644
index f7cdffd..0000000
--- a/src/network/http_routes/message.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use axum::Router;
-use axum::routing::post;
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- // .route("/auth/", post(join_master_server))
-}
\ No newline at end of file
diff --git a/src/network/http_routes/mod.rs b/src/network/http_routes/mod.rs
deleted file mode 100644
index 7c930d7..0000000
--- a/src/network/http_routes/mod.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub mod user;
-pub mod channel;
-pub mod message;
-pub mod websocket;
-pub mod master;
-mod sub_server;
\ No newline at end of file
diff --git a/src/network/http_routes/sub_server.rs b/src/network/http_routes/sub_server.rs
deleted file mode 100644
index 57b383a..0000000
--- a/src/network/http_routes/sub_server.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use axum::Router;
-use axum::routing::post;
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- // .route("/auth/", post(join_master_server))
-}
-
diff --git a/src/network/http_routes/user.rs b/src/network/http_routes/user.rs
deleted file mode 100644
index f7cdffd..0000000
--- a/src/network/http_routes/user.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use axum::Router;
-use axum::routing::post;
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- // .route("/auth/", post(join_master_server))
-}
\ No newline at end of file
diff --git a/src/network/http_routes/websocket.rs b/src/network/http_routes/websocket.rs
deleted file mode 100644
index f7cdffd..0000000
--- a/src/network/http_routes/websocket.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use axum::Router;
-use axum::routing::post;
-use crate::network::http::HttpState;
-
-pub fn create_router() -> Router {
- Router::new()
- // .route("/auth/", post(join_master_server))
-}
\ No newline at end of file
diff --git a/src/network/protocol.rs b/src/network/protocol.rs
deleted file mode 100644
index 6eca83a..0000000
--- a/src/network/protocol.rs
+++ /dev/null
@@ -1,246 +0,0 @@
-use std::collections::HashSet;
-use bytes::{Bytes, BytesMut, Buf, BufMut};
-use std::net::SocketAddr;
-use uuid::Uuid;
-use strum::{EnumIter, FromRepr};
-
-#[repr(u8)]
-#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, EnumIter, FromRepr)]
-pub enum UDPMessageType {
- Ping = 0,
- Audio = 1,
- // Futurs types ici...
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum UDPMessageData {
- // Client messages - Zero-copy avec Bytes
- ClientPing { message_id: Uuid },
- ClientAudio { sequence: u16, data: Bytes },
-
- // Server messages
- ServerPing { message_id: Uuid },
- ServerAudio { user: Uuid, sequence: u16, data: Bytes },
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct UDPMessage {
- pub data: UDPMessageData,
- pub address: SocketAddr,
- pub size: usize,
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct UdpBroadcastMessage {
- pub data: UDPMessageData,
- pub addresses: HashSet, // ou Vec selon besoins
- pub size: usize,
-}
-
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum ParseError {
- EmptyData,
- InvalidData,
- InvalidMessageType,
- InvalidUuid,
-}
-
-impl From for ParseError {
- fn from(_: uuid::Error) -> Self {
- ParseError::InvalidUuid
- }
-}
-
-impl UDPMessageData {
- // Parsing zero-copy depuis Bytes
- pub fn from_client_bytes(mut data: Bytes) -> Result {
- if data.is_empty() {
- return Err(ParseError::EmptyData);
- }
-
- let msg_type = data.get_u8(); // Consomme 1 byte
-
- match msg_type {
- 0 => { // Ping
- if data.remaining() < 16 {
- return Err(ParseError::InvalidData);
- }
- let uuid_bytes = data.split_to(16); // Zero-copy split
- let message_id = Uuid::from_slice(&uuid_bytes)?;
- Ok(Self::ClientPing { message_id })
- }
- 1 => { // Audio
- if data.remaining() < 2 {
- return Err(ParseError::InvalidData);
- }
- let sequence = data.get_u16(); // Big-endian par défaut
- let audio_data = data; // Le reste pour l'audio
- Ok(Self::ClientAudio { sequence, data: audio_data })
- }
- _ => Err(ParseError::InvalidMessageType),
- }
- }
-
- // Constructeurs server
- pub fn server_ping(message_id: Uuid) -> Self {
- Self::ServerPing { message_id }
- }
-
- pub fn server_audio(user: Uuid, sequence: u16, data: Bytes) -> Self {
- Self::ServerAudio { user, sequence, data }
- }
-
- // Sérialisation optimisée avec BytesMut
- pub fn to_bytes(&self) -> Bytes {
- match self {
- Self::ServerPing { message_id } => {
- let mut buf = BytesMut::with_capacity(17);
- buf.put_u8(0); // Message type
- buf.put_slice(message_id.as_bytes());
- buf.freeze()
- }
- Self::ServerAudio { user, sequence, data } => {
- let mut buf = BytesMut::with_capacity(19 + data.len());
- buf.put_u8(1); // Message type
- buf.put_slice(user.as_bytes());
- buf.put_u16(*sequence);
- buf.put_slice(data);
- buf.freeze()
- }
- _ => panic!("Client messages cannot be serialized"),
- }
- }
-
- pub fn to_vec(&self) -> Vec {
- // pas très optimisé
- self.to_bytes().to_vec()
- }
-
- pub fn message_type(&self) -> UDPMessageType {
- match self {
- Self::ClientPing { .. } | Self::ServerPing { .. } => UDPMessageType::Ping,
- Self::ClientAudio { .. } | Self::ServerAudio { .. } => UDPMessageType::Audio,
- }
- }
-
- // Calcule la taille du message sérialisé
- pub fn size(&self) -> usize {
- match self {
- Self::ClientPing { .. } | Self::ServerPing { .. } => 17, // 1 + 16 (UUID)
- Self::ClientAudio { data, .. } => 3 + data.len(), // 1 + 2 + audio_data
- Self::ServerAudio { data, .. } => 19 + data.len(), // 1 + 16 + 2 + audio_data
- }
- }
-}
-
-impl UDPMessage {
- // Parsing depuis slice -> Bytes (zero-copy si possible)
- pub fn from_client_bytes(address: SocketAddr, data: &[u8]) -> Result {
- let original_size = data.len();
- let bytes = Bytes::copy_from_slice(data); // Seule allocation
- let data = UDPMessageData::from_client_bytes(bytes)?;
- Ok(Self {
- data,
- address,
- size: original_size
- })
- }
-
- // Constructeurs server
- pub fn server_ping(address: SocketAddr, message_id: Uuid) -> Self {
- let data = UDPMessageData::server_ping(message_id);
- let size = data.size();
- Self { data, address, size }
- }
-
- pub fn server_audio(address: SocketAddr, user: Uuid, sequence: u16, data: Bytes) -> Self {
- let msg_data = UDPMessageData::server_audio(user, sequence, data);
- let size = msg_data.size();
- Self { data: msg_data, address, size }
- }
-
- // Helpers
- pub fn to_bytes(&self) -> Bytes {
- self.data.to_bytes()
- }
-
- pub fn to_vec(&self) -> Vec {
- self.data.to_vec()
- }
-
- pub fn message_type(&self) -> UDPMessageType {
- self.data.message_type()
- }
-
- // Getters
- pub fn size(&self) -> usize {
- self.size
- }
-
- pub fn address(&self) -> SocketAddr {
- self.address
- }
-}
-
-impl UDPMessage {
- // Helpers pour récupérer certain éléments des messages
- pub fn get_message_id(&self) -> Option {
- match &self.data {
- UDPMessageData::ClientPing { message_id } => Some(*message_id),
- UDPMessageData::ServerPing { message_id } => Some(*message_id),
- _ => None,
- }
- }
-}
-
-// Helper pour compatibilité avec UDPMessageType
-impl UDPMessageType {
- pub fn from_message(data: &[u8]) -> Option {
- if data.is_empty() {
- return None;
- }
- Self::from_repr(data[0])
- }
-}
-
-impl UdpBroadcastMessage {
- // Constructeurs server
- pub fn server_ping(addresses: HashSet, message_id: Uuid) -> Self {
- let data = UDPMessageData::server_ping(message_id);
- let size = data.size();
- Self { data, addresses, size }
- }
-
- pub fn server_audio(addresses: HashSet, user: Uuid, sequence: u16, data: Bytes) -> Self {
- let msg_data = UDPMessageData::server_audio(user, sequence, data);
- let size = msg_data.size();
- Self { data: msg_data, addresses, size }
- }
-
- // Conversion vers messages individuels (pour compatibilité)
- pub fn to_individual_messages(&self) -> Vec {
- self.addresses.iter().map(|&addr| {
- UDPMessage {
- data: self.data.clone(),
- address: addr,
- size: self.size,
- }
- }).collect()
- }
-
- // Helpers
- pub fn to_bytes(&self) -> Bytes {
- self.data.to_bytes()
- }
-
- pub fn addresses(&self) -> &HashSet {
- &self.addresses
- }
-
- pub fn size(&self) -> usize {
- self.size
- }
-}
-
-
diff --git a/src/network/udp.rs b/src/network/udp.rs
deleted file mode 100644
index e29c599..0000000
--- a/src/network/udp.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use tokio::net::UdpSocket;
-use std::error::Error;
-use std::sync::Arc;
-use tokio::task::AbortHandle;
-use crate::domain::event::{Event, EventBus};
-use crate::network::protocol::{UDPMessage, UdpBroadcastMessage};
-
-#[derive(Clone)]
-pub struct UdpServer {
- event_bus: EventBus,
- socket: Arc,
- abort_handle: Option,
-}
-
-impl UdpServer {
- pub async fn new(event_bus: EventBus, addr: &str) -> Self {
- let socket = UdpSocket::bind(addr).await.unwrap();
- let addr = socket.local_addr().unwrap();
- println!("Socket UDP lié avec succès on {}", addr);
-
- Self {
- event_bus,
- socket: Arc::new(socket),
- abort_handle: None
- }
- }
-
- pub async fn start(&mut self) -> Result<(), Box> {
- println!("Démarrage du serveur UDP...");
- let event_bus = self.event_bus.clone();
- let socket = self.socket.clone();
-
- let recv_task = tokio::spawn(async move {
- // Buffer réutilisable pour éviter les allocations
- let mut buf = vec![0u8; 1500];
-
- loop {
- match socket.recv_from(&mut buf).await {
- Ok((size, address)) => {
- // Slice du buffer pour éviter de copier des données inutiles
- if let Ok(message) = UDPMessage::from_client_bytes(address, &buf[..size]) {
- event_bus.emit(Event::UdpIn(message)).await;
- }
- // Sinon, on ignore silencieusement le message malformé
- }
- Err(e) => {
- match e.kind() {
- std::io::ErrorKind::ConnectionReset |
- std::io::ErrorKind::ConnectionAborted => {
- // Silencieux pour les déconnexions normales
- continue;
- }
- _ => {
- println!("Erreur UDP: {}", e);
- continue;
- }
- }
- }
- }
- }
- });
-
- self.abort_handle = Some(recv_task.abort_handle());
- Ok(())
- }
-
- pub async fn send_udp_message(&self, message: &UDPMessage) -> bool {
- match self.socket.send_to(&message.to_bytes(), message.address()).await {
- Ok(_size) => {
- self.event_bus.emit(Event::UdpOut(message.clone())).await;
- true
- }
- Err(e) => {
- println!("Erreur lors de l'envoi du message à {}: {}", message.address(), e);
- false
- }
- }
- }
-
- pub async fn broadcast_udp_message(&self, message: &UdpBroadcastMessage) -> bool {
- let bytes = message.to_bytes();
-
- for &address in message.addresses() {
- match self.socket.send_to(&bytes, address).await {
- Ok(_) => {
- // Emit individual event pour tracking
- let individual_msg = UDPMessage {
- data: message.data.clone(),
- address,
- size: message.size,
- };
- self.event_bus.emit(Event::UdpOut(individual_msg)).await;
- }
- Err(e) => {
- println!("Erreur broadcast vers {}: {}", address, e);
- }
- }
- }
-
- true
- }
-
-}
\ No newline at end of file
diff --git a/src/network/udp/mod.rs b/src/network/udp/mod.rs
new file mode 100644
index 0000000..bfe15ae
--- /dev/null
+++ b/src/network/udp/mod.rs
@@ -0,0 +1 @@
+pub mod server;
\ No newline at end of file
diff --git a/src/network/udp/server.rs b/src/network/udp/server.rs
new file mode 100644
index 0000000..82472d2
--- /dev/null
+++ b/src/network/udp/server.rs
@@ -0,0 +1,160 @@
+/*
+Utilisation de UDP pour la communication entre le client et le serveur.
+L'idée est d'utilise SO_REUSEPORT et de router les paquets aux clients dans le channel correspondant.
+Il est donc important de garder une table de routage entre les channels et les sockets, comme ça le serveur sait à qui re-router le paquet.
+Dans l'ancienne version on utilisait un mpsc, mais celà casse l'intérêt du SO_REUSEPORT.
+Avec cette logique, on évite de transporter le paquet de thread en thread et potentiellement de devoir faire des copies.
+*/
+
+use std::io;
+use std::net::{SocketAddr};
+use tokio::net::UdpSocket;
+use tokio::runtime::Handle;
+use tokio::task;
+use crate::utils::shared_store::{SharedArcMap, SharedArcVec};
+use crate::utils::toolbox::number_of_cpus;
+use crate::utils::logger::ContextLogger;
+use crate::{log_info, log_warn, log_error, log_debug};
+
+
+pub struct UdpServer {
+ // table de routage channel -> socket
+ table_router: SharedArcMap>,
+
+ // binds
+ bind_addr: SocketAddr,
+ workers: usize,
+ buf_size: usize,
+
+ // logger
+ logger: ContextLogger,
+}
+
+impl UdpServer {
+ pub fn new(bind_addr: &str) -> io::Result {
+ // convert bind_addr
+ let bind_addr = match bind_addr.parse::() {
+ Ok(addr) => addr,
+ Err(e) => {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid bind address '{}': {}", bind_addr, e)))
+ },
+ };
+
+ let logger = ContextLogger::new("UDP");
+ logger.info(&format!("Creating UDP server on {}", bind_addr));
+
+ Ok(Self {
+ table_router: SharedArcMap::new(),
+ bind_addr,
+ workers: number_of_cpus(),
+ buf_size: 1500,
+ logger,
+ })
+ }
+
+ pub async fn run(self) -> io::Result<()> {
+ // S'assure qu'on est sur une runtime (utile si appelé hors #[tokio::main])
+ let _ = Handle::try_current().map_err(|e| {
+ self.logger.error(&format!("Runtime Tokio not available: {}", e));
+ io::Error::new(io::ErrorKind::Other, e)
+ })?;
+
+ #[cfg(unix)]
+ {
+ self.run_unix().await
+ }
+ #[cfg(windows)]
+ {
+ self.run_windows().await
+ }
+ #[cfg(not(any(unix, windows)))]
+ {
+ log_error!(self.logger, "OS not supported");
+ Err(io::Error::new(io::ErrorKind::Other, "OS non supporté"))
+ }
+ }
+
+ // -------------------------
+ // Impl Linux/macOS (SO_REUSEPORT)
+ // -------------------------
+ #[cfg(unix)]
+ async fn run_unix(self) -> io::Result<()> {
+ use socket2::{Domain, Protocol, Socket, Type};
+
+ self.logger.info(&format!("Starting UDP server on {} with {} workers - Unix mode", self.bind_addr, self.workers));
+
+ let mut handles = Vec::with_capacity(self.workers);
+ for id in 0..self.workers {
+ let logger = self.logger.with_sub_context(&format!("WORKER_{}", id));
+ let bind_addr = self.bind_addr;
+
+ // Create a socket UDP with SO_REUSEPORT
+ let domain = match bind_addr {
+ SocketAddr::V4(_) => Domain::IPV4,
+ SocketAddr::V6(_) => Domain::IPV6,
+ };
+ let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
+ sock.set_reuse_address(true)?;
+ sock.set_reuse_port(true)?;
+ sock.bind(&bind_addr.into())?;
+
+ let std_sock = std::net::UdpSocket::from(sock);
+ std_sock.set_nonblocking(true)?;
+ let udp = UdpSocket::from_std(std_sock)?;
+
+ let buf_size = self.buf_size;
+ let h = task::spawn(async move {
+ if let Err(e) = Self::worker_loop(id, udp, buf_size, logger).await {
+ eprintln!("[worker {id}] erreur: {e}");
+ }
+ });
+ handles.push(h);
+ }
+
+ for h in handles {
+ let _ = h.await;
+ }
+ Ok(())
+ }
+
+ // -------------------------
+ // Impl Windows (1 socket partagé, N tâches concurrentes)
+ // -------------------------
+ #[cfg(windows)]
+ async fn run_windows(self) -> io::Result<()> {
+ self.logger.info(&format!("Starting UDP server on {} with {} workers - Win mode", self.bind_addr, self.workers));
+
+ let udp = UdpSocket::bind(self.bind_addr).await?;
+ let udp = Arc::new(udp);
+
+ let mut handles = Vec::with_capacity(self.workers);
+ for id in 0..self.workers {
+ let logger = self.logger.with_sub_context(&format!("WORKER_{}", id));
+ let sock = udp.clone();
+ let buf_size = self.buf_size;
+ let h = task::spawn(async move {
+ if let Err(e) = worker_loop(id, (*sock).clone(), buf_size, logger).await {
+ eprintln!("[worker {id}] erreur: {e}");
+ }
+ });
+ handles.push(h);
+ }
+
+ for h in handles {
+ let _ = h.await;
+ }
+ Ok(())
+ }
+
+ pub async fn worker_loop(id: usize, socket: UdpSocket, buf_size: usize, logger: ContextLogger) -> io::Result<()> {
+ let mut buf = vec![0u8; buf_size];
+ logger.info("listening");
+ loop {
+ let (n, peer) = socket.recv_from(&mut buf).await?;
+ // Traitement: ici, simple echo
+ // Remplacez par votre logique (routage canal -> client, etc.)
+ eprintln!("[worker {id}] reçu {n}o de {peer}");
+ socket.send_to(&buf[..n], &peer).await?;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/network/udp_back.rs b/src/network/udp_back.rs
deleted file mode 100644
index b5bd8d1..0000000
--- a/src/network/udp_back.rs
+++ /dev/null
@@ -1,193 +0,0 @@
-use tokio::net::UdpSocket;
-use tokio::sync::RwLock;
-use std::error::Error;
-use std::net::SocketAddr;
-use std::sync::Arc;
-use std::time::Duration;
-use dashmap::DashMap;
-use tokio::task::AbortHandle;
-use tokio::time::{sleep, Instant};
-use crate::domain::client::Client;
-use crate::domain::event::{Event, EventBus};
-use crate::network::protocol::{UdpClientMessage, UdpServerMessage};
-
-#[derive(Clone)]
-pub struct UdpServer {
- event_bus: EventBus,
- socket: Arc,
- abort_handle: Option,
- clients: Arc>,
-}
-
-impl UdpServer {
- pub async fn new(event_bus: EventBus, addr: &str) -> Self {
- let socket = UdpSocket::bind(addr).await.unwrap();
- let addr = socket.local_addr().unwrap();
- println!("Socket UDP lié avec succès on {}", addr);
-
- Self {
- event_bus,
- socket: Arc::new(socket),
- abort_handle: None,
- clients: Arc::new(DashMap::new()),
- }
- }
-
- pub async fn start(&mut self) -> Result<(), Box> {
- println!("Démarrage du serveur UDP...");
- let event_bus = self.event_bus.clone();
- let socket = self.socket.clone();
- let clients = self.clients.clone();
-
- let recv_task = tokio::spawn(async move {
- let mut buf = [0u8; 1500];
- loop {
- match socket.recv_from(&mut buf).await {
- Ok((size, address)) => {
- // Ajouter le client à la liste
- // todo : solution vraiment pas idéal, il faudrait vraiment la repenser avec un système helo/bye
- if !clients.contains_key(&address) {
- let client = Client::new(address);
- clients.insert(address, client);
- println!("Nouveau client connecté: {}", address);
- }else {
- let mut client = clients.get_mut(&address).unwrap();
- client.update_last_seen();
- }
-
- if let Ok(message) = UdpClientMessage::from_bytes(&buf[..size]) {
- let event = Event::UdpIn { address, size, message };
- event_bus.emit(event).await;
- } else {
- println!("Erreur lors du parsing du message de {}: {:?}", address, &buf[..size]);
- }
- }
- Err(e) => {
- match e.kind() {
- std::io::ErrorKind::ConnectionReset |
- std::io::ErrorKind::ConnectionAborted => {
- // Silencieux pour les déconnexions normales
- continue;
- }
- _ => {
- println!("Erreur UDP: {}", e);
- continue;
- }
- }
- }
- }
- }
- });
-
- self.abort_handle = Some(recv_task.abort_handle());
- Ok(())
- }
-
- pub async fn send(&self, address: SocketAddr, message: UdpServerMessage) -> bool {
- let event_bus = self.event_bus.clone();
- match self.socket.send_to(&message.to_byte(), address).await {
- Ok(size) => {
- event_bus.emit(Event::UdpOut { address, size, message }).await;
- true
- }
- Err(e) => {
- println!("Erreur lors de l'envoi du message à {}: {}", address, e);
- // Optionnel : retirer le client si l'adresse est invalide
- self.remove_client(address).await;
- false
- }
- }
- }
-
- pub async fn group_send(&self, addr_list: Vec, message: UdpServerMessage) -> bool {
- if addr_list.is_empty() {
- return true;
- }
-
- let socket = self.socket.clone();
- let clients = self.clients.clone();
-
- let send_tasks: Vec<_> = addr_list.into_iter().map(|address| {
- let event_bus = self.event_bus.clone();
- let message_clone = message.clone();
- let socket_clone = socket.clone();
- let clients_clone = clients.clone();
-
- tokio::spawn(async move {
- match socket_clone.send_to(&message_clone.to_byte(), address).await {
- Ok(size) => {
- event_bus.emit(Event::UdpOut { address, size, message: message_clone }).await;
- true
- }
- Err(e) => {
- println!("Erreur lors de l'envoi du message à {}: {}", address, e);
- // Optionnel : retirer le client si l'adresse est invalide
- if clients_clone.contains_key(&address) {
- clients_clone.remove(&address);
- println!("Client {} retiré de la liste", address);
- }
- false
- }
- }
- })
- }).collect();
-
- let mut all_success = true;
- for task in send_tasks {
- match task.await {
- Ok(success) => {
- if !success {
- all_success = false;
- }
- }
- Err(_) => {
- all_success = false;
- }
- }
- }
-
- all_success
- }
-
- pub async fn all_send(&self, message: UdpServerMessage) -> bool {
- let client_addresses = self.get_clients().await;
- self.group_send(client_addresses, message).await
- }
-
- pub async fn get_clients(&self) -> Vec {
- self.clients.iter()
- .map(|entry| *entry.key())
- .collect()
- }
-
- // Nouvelle méthode pour nettoyer les clients déconnectés
- async fn remove_client(&self, address: SocketAddr) {
- if self.clients.contains_key(&address){
- self.clients.remove(&address);
- println!("Client {} retiré de la liste", address);
- }else {
- println!("Client {} n'est pas dans la liste", address);
- }
- }
-
- // Méthode pour nettoyer les clients inactifs
- pub async fn cleanup_inactive_clients(&self) {
- let timeout = Duration::from_secs(10);
- let now = Instant::now();
- let mut to_remove = Vec::new();
-
- for entry in self.clients.iter() {
- let address = *entry.key();
- let client = entry.value();
-
- if now.duration_since(client.last_seen()) > timeout {
- to_remove.push(address);
- }
- }
-
- for address in &to_remove {
- println!("Suppression du client {}", address);
- self.clients.remove(address);
- }
- }
-}
\ No newline at end of file
diff --git a/src/runtime/dispatcher.rs b/src/runtime/dispatcher.rs
deleted file mode 100644
index 967bbc7..0000000
--- a/src/runtime/dispatcher.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-use std::sync::Arc;
-use std::thread;
-use std::time::Duration;
-use tokio::sync::mpsc;
-use tokio::task::AbortHandle;
-use crate::domain::client::{Client, ClientManager};
-use crate::domain::event::{Event, EventBus};
-use crate::network::protocol::{UDPMessageType, UDPMessage, UdpBroadcastMessage, UDPMessageData};
-use crate::network::udp::UdpServer;
-use crate::store::store_service::StoreService;
-
-#[derive(Clone)]
-pub struct Dispatcher {
- event_bus: Arc,
-
- udp_server: Arc,
- client_manager: Arc,
-
- store: StoreService,
-}
-
-impl Dispatcher {
- pub async fn new(event_bus: EventBus, udp_server: UdpServer, client_manager: ClientManager, store: StoreService) -> Self {
- Self {
- event_bus: Arc::new(event_bus),
- udp_server: Arc::new(udp_server),
- client_manager: Arc::new(client_manager),
- store,
- }
- }
-
- pub async fn start(&self, receiver: kanal::AsyncReceiver) {
- println!("Dispatcher démarré sur le thread : {:?}", std::thread::current().id());
-
- let (_udp_in_abort_handle, udp_in_sender) = self.udp_in_handler().await;
-
- while let Ok(event) = receiver.recv().await {
- match event {
- Event::UdpIn(message) => {
- let _ = udp_in_sender.send(message).await;
- // // println!("Message reçu de {}: {:?}", address, message);
- // let udp_server = self.udp_server.clone();
- // tokio::spawn(async move {
- // match message {
- // UdpClientMessage::Ping {message_id} => {
- // let send = UdpServerMessage::ping(message_id);
- // let _ = udp_server.all_send(send);
- // }
- // UdpClientMessage::Audio {sequence, data} => {
- // let tmp_user_id = Uuid::new_v4();
- // let send = UdpServerMessage::audio(tmp_user_id, sequence, data);
- // let _ = udp_server.all_send(send).await;
- // }
- // }
- // });
- }
- Event::UdpOut(message) => {
- // println!("Message envoyé à {}: {:?}", address, message);
- }
- Event::TickSeconds => {
- self.client_manager.cleanup(Duration::from_secs(10));
- }
- _ => {
- println!("Event non prit en charge : {:?}", event)
- }
- }
- }
- }
-
- pub async fn udp_in_handler(&self) -> (AbortHandle, mpsc::Sender) {
- let (sender, mut consumer) = mpsc::channel::(1024);
- let udp_server = self.udp_server.clone();
- let client_manager = self.client_manager.clone();
-
- let task = tokio::spawn(async move {
- while let Some(message) = consumer.recv().await {
- // Traitement direct du message sans double parsing
- match message.message_type() {
- UDPMessageType::Ping => {
- println!("ping from {:?}", message);
- let response_message = UDPMessage::server_ping(message.address, message.get_message_id().unwrap());
-
- if client_manager.client_exists(message.address) {
- client_manager.update_client_last_seen(message.address);
- }else {
- client_manager.add(Client::new(message.address));
- println!("new client: {:?}", message.address);
- }
- let _ = udp_server.send_udp_message(&response_message).await;
- }
- UDPMessageType::Audio => {
- if let UDPMessageData::ClientAudio { sequence, data } = &message.data {
- let addresses = client_manager.get_all_addresses();
- if let Some(speaker_uuid) = client_manager.get_uuid_by_address(message.address) {
- let response_message = UdpBroadcastMessage::server_audio(addresses, speaker_uuid, *sequence, data.clone());
- let _ = udp_server.broadcast_udp_message(&response_message).await;
- } else {
- // Tu peux gérer ici le cas où l’UUID n’est pas trouvé (optionnel)
- // println!("UUID non trouvé pour l'adresse: {:?}", message.address);
- }
- }
-
- }
- }
- }
- });
-
- (task.abort_handle(), sender)
- }
-
-
-
-
-}
\ No newline at end of file
diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs
deleted file mode 100644
index ebffa3f..0000000
--- a/src/runtime/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod dispatcher;
\ No newline at end of file
diff --git a/src/store/migrations/001_init.sqlite.sql b/src/store/migrations/001_init.sqlite.sql
deleted file mode 100644
index 19a8306..0000000
--- a/src/store/migrations/001_init.sqlite.sql
+++ /dev/null
@@ -1,70 +0,0 @@
--- Add migration script here
-
--- Création de la table user
-CREATE TABLE user
-(
- id TEXT PRIMARY KEY NOT NULL,
- username TEXT NOT NULL UNIQUE,
- email TEXT,
- avatar_url TEXT,
- created_at TEXT NOT NULL,
- updated_at TEXT NOT NULL
-);
-
--- Création de la table sub_server
-CREATE TABLE sub_server
-(
- id TEXT PRIMARY KEY NOT NULL,
- name TEXT NOT NULL,
- description TEXT,
- owner_id TEXT NOT NULL,
- created_at TEXT NOT NULL,
- updated_at TEXT NOT NULL,
- FOREIGN KEY (owner_id) REFERENCES user (id)
-);
-
--- Création de la table channel
-CREATE TABLE channel
-(
- id TEXT PRIMARY KEY NOT NULL,
- name TEXT NOT NULL,
- description TEXT,
- channel_type TEXT NOT NULL,
- sub_server_id TEXT NOT NULL,
- created_at TEXT NOT NULL,
- updated_at TEXT NOT NULL,
- FOREIGN KEY (sub_server_id) REFERENCES sub_server (id)
-);
-
--- Création de la table message
-CREATE TABLE message
-(
- id TEXT PRIMARY KEY NOT NULL,
- content TEXT NOT NULL,
- author_id TEXT NOT NULL,
- channel_id TEXT NOT NULL,
- created_at TEXT NOT NULL,
- updated_at TEXT NOT NULL,
- FOREIGN KEY (author_id) REFERENCES user (id),
- FOREIGN KEY (channel_id) REFERENCES channel (id)
-);
-
--- Table de liaison pour les utilisateurs et sub_servers (relation N-N)
-CREATE TABLE sub_server_user
-(
- id TEXT PRIMARY KEY NOT NULL,
- sub_server_id TEXT NOT NULL,
- user_id TEXT NOT NULL,
- joined_at TEXT NOT NULL,
- role TEXT,
- FOREIGN KEY (sub_server_id) REFERENCES sub_server (id),
- FOREIGN KEY (user_id) REFERENCES user (id),
- UNIQUE (sub_server_id, user_id)
-);
-
--- Index pour améliorer les performances
-CREATE INDEX idx_channel_sub_server ON channel (sub_server_id);
-CREATE INDEX idx_message_channel ON message (channel_id);
-CREATE INDEX idx_message_author ON message (author_id);
-CREATE INDEX idx_sub_server_user_sub_server ON sub_server_user (sub_server_id);
-CREATE INDEX idx_sub_server_user_user ON sub_server_user (user_id);
\ No newline at end of file
diff --git a/src/store/models/channel.rs b/src/store/models/channel.rs
deleted file mode 100644
index 9c7ea79..0000000
--- a/src/store/models/channel.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::hash::{Hash, Hasher};
-// use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-// use uuid::Uuid;
-use sqlx::types::{Uuid, chrono::{{ DateTime, Utc}}};
-
-#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
-pub struct Channel {
- pub id: Uuid,
- pub sub_server_id: Uuid,
- pub channel_type: ChannelType,
- pub name: String,
- pub created_at: DateTime,
-}
-
-#[derive(Debug, Clone, sqlx::Type, Serialize, Deserialize)]
-#[repr(i32)]
-pub enum ChannelType {
- Text = 0,
- Voice = 1,
-}
-
-impl Channel {
- pub fn new(sub_server_id: Uuid, channel_type: ChannelType, name: String) -> Self {
- Self {
- id: Uuid::new_v4(),
- sub_server_id,
- channel_type,
- name,
- created_at: Utc::now(),
- }
- }
-}
-
-impl PartialEq for Channel {
- fn eq(&self, other: &Self) -> bool {
- self.id == other.id
- }
-}
-
-impl Eq for Channel {}
-
-impl Hash for Channel {
- fn hash(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
\ No newline at end of file
diff --git a/src/store/models/link_sub_server_user.rs b/src/store/models/link_sub_server_user.rs
deleted file mode 100644
index c40dff0..0000000
--- a/src/store/models/link_sub_server_user.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
-
-#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
-pub struct LinkSubServerUser {
- pub id: Option,
- pub sub_server_id: Uuid,
- pub user_id: Uuid,
- pub display_username: String,
- pub joined_at: DateTime,
- pub last_seen_at: Option>,
- pub is_admin: bool,
-}
-
-impl LinkSubServerUser {
- pub fn new(
- sub_server_id: Uuid,
- user_id: Uuid,
- display_username: String
- ) -> Self {
- Self {
- id: None,
- sub_server_id,
- user_id,
- display_username,
- joined_at: Utc::now(),
- last_seen_at: None,
- is_admin: false,
- }
- }
-}
diff --git a/src/store/models/message.rs b/src/store/models/message.rs
deleted file mode 100644
index fef3e78..0000000
--- a/src/store/models/message.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
-
-#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
-pub struct Message {
- pub id: Uuid,
- pub channel_id: Uuid,
- pub user_id: Uuid,
- pub content: String,
- pub created_at: DateTime,
-}
-
-impl Message {
- pub fn new(channel_id: Uuid, user_id: Uuid, content: String) -> Self {
- Self {
- id: Uuid::new_v4(),
- channel_id,
- user_id,
- content,
- created_at: Utc::now(),
- }
- }
-}
\ No newline at end of file
diff --git a/src/store/models/mod.rs b/src/store/models/mod.rs
deleted file mode 100644
index 3acd86e..0000000
--- a/src/store/models/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-pub mod sub_server;
-pub mod channel;
-pub mod user;
-pub mod message;
-pub mod link_sub_server_user;
-
-pub use sub_server::*;
-pub use channel::*;
-pub use user::*;
-pub use message::*;
-pub use link_sub_server_user::*;
\ No newline at end of file
diff --git a/src/store/models/sub_server.rs b/src/store/models/sub_server.rs
deleted file mode 100644
index 7c07336..0000000
--- a/src/store/models/sub_server.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-// L'application peut avoir plusieurs sous servers
-
-use std::hash::{Hash, Hasher};
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
-use crate::store::models::Channel;
-
-#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
-pub struct SubServer {
- pub id: Uuid,
- pub name: String,
- pub password: String, // voir si on le hash, mais sera certainement pas nécessaire.
- pub created_at: DateTime,
-}
-
-impl SubServer {
- pub fn new(name: String, password: String) -> Self {
- Self {
- id: Uuid::new_v4(),
- name,
- password,
- created_at: Utc::now(),
- }
- }
-}
-
-impl PartialEq for SubServer {
- fn eq(&self, other: &Self) -> bool {
- self.id == other.id
- }
-}
-
-impl Eq for SubServer {}
-
-impl Hash for SubServer {
- fn hash(&self, state: &mut H) {
- self.id.hash(state);
- }
-}
\ No newline at end of file
diff --git a/src/store/models/user.rs b/src/store/models/user.rs
deleted file mode 100644
index 9f6b9c3..0000000
--- a/src/store/models/user.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use chrono::{DateTime, Utc};
-use serde::{Deserialize, Serialize};
-use uuid::Uuid;
-
-#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
-pub struct User {
- pub id: Uuid,
- pub username: String,
- pub pub_key: String, // l'identification se fera par clé public/privé, comme teamspeak
- pub created_at: DateTime,
-}
-
-
diff --git a/src/store/repositories/channel_repository.rs b/src/store/repositories/channel_repository.rs
deleted file mode 100644
index b734873..0000000
--- a/src/store/repositories/channel_repository.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-use std::sync::Arc;
-use sqlx::SqlitePool;
-use uuid::Uuid;
-use crate::store::models::channel::Channel;
-use crate::store::models::Message;
-use crate::store::store_service::StoreService;
-use crate::utils::shared_store::SharedArcMap;
-
-#[derive(Clone)]
-pub struct ChannelRepository {
- store: StoreService,
-}
-
-impl ChannelRepository {
- pub fn new(store: StoreService) -> Self {
- Self {
- store
- }
- }
-}
-
-impl ChannelRepository {
- // getters
- pub async fn all(&self) -> Vec> {
- self.store.channels.values().collect()
- }
-
- pub async fn get(&self, id: Uuid) -> Option> {
- self.store.channels.get(&id)
- }
-}
-
-// pub id: Uuid,
-// pub sub_server_id: Uuid,
-// pub channel_type: ChannelType,
-// pub name: String,
-// pub created_at: DateTime,
-impl ChannelRepository {
- // writers
- pub async fn create(&self, mut channel: Channel) -> Result, sqlx::Error> {
- sqlx::query(
- "INSERT INTO channel (id, sub_server, channel_type, name, created_at) VALUES (?, ?, ?, ?, ?)"
- )
- .bind(&channel.id)
- .bind(&channel.sub_server_id)
- .bind(&channel.channel_type)
- .bind(&channel.name)
- .bind(&channel.created_at)
- .execute(&self.store.db)
- .await?;
-
- // ajouter au cache
- let arc_server = Arc::new(channel.clone());
- self.store.channels.insert_arc(channel.id, arc_server.clone());
-
- Ok(arc_server)
- }
-
- pub async fn save(&self, sub_server: &Channel) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE channel SET name = ? WHERE id = ?"
- )
- .bind(&sub_server.name)
- .bind(&sub_server.id)
- .execute(&self.store.db)
- .await?;
-
- // Mettre à jour le cache
- self.store.channels.insert(sub_server.id, sub_server.clone());
-
- Ok(())
- }
-
- pub async fn delete(&self, id: Uuid) -> Result {
- let rows_affected = sqlx::query("DELETE FROM channel WHERE id = ?")
- .bind(&id)
- .execute(&self.store.db)
- .await?
- .rows_affected();
-
- if rows_affected > 0 {
- self.store.channels.remove(&id);
- Ok(true)
- } else {
- Ok(false)
- }
- }
-}
-
-impl ChannelRepository {
- // getters (db)
- pub async fn db_all(&self) -> Result, sqlx::Error> {
- sqlx::query_as("SELECT * FROM channel")
- .fetch_all(&self.store.db)
- .await
- }
-}
\ No newline at end of file
diff --git a/src/store/repositories/link_sub_server_user_repository.rs b/src/store/repositories/link_sub_server_user_repository.rs
deleted file mode 100644
index c3b423b..0000000
--- a/src/store/repositories/link_sub_server_user_repository.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use std::sync::Arc;
-use uuid::Uuid;
-use chrono::{DateTime, Utc};
-use sqlx::SqlitePool;
-use crate::store::models::link_sub_server_user::LinkSubServerUser;
-use crate::store::store_service::StoreService;
-use crate::utils::shared_store::SharedArcVec;
-
-#[derive(Clone)]
-pub struct LinkSubServerUserRepository {
- store: StoreService
-}
-
-impl LinkSubServerUserRepository {
- pub fn new(store: StoreService) -> Self {
- Self { store }
- }
-}
-
-impl LinkSubServerUserRepository {
- // getters
- /// Obtenir une relation spécifique
- pub async fn get_relation(&self, sub_server_id: Uuid, user_id: Uuid) -> Option {
- self.store.sub_server_users.iter()
- .find(|relation| {
- relation.sub_server_id == sub_server_id && relation.user_id == user_id
- })
- .map(|arc| (*arc).clone())
- }
-
-
- pub async fn exists(&self, sub_server_id: Uuid, user_id: Uuid) -> bool {
- self.get_relation(sub_server_id, user_id).await.is_some()
- }
-}
-
-impl LinkSubServerUserRepository {
- // writers
- /// Créer une nouvelle relation
- pub async fn create(&self, through: LinkSubServerUser) -> Result {
- // Vérifier que la relation n'existe pas déjà
- if self.exists(through.sub_server_id, through.user_id).await {
- return Err(sqlx::Error::RowNotFound); // Ou une erreur custom
- }
-
- // Insérer en base
- sqlx::query(
- "INSERT INTO sub_server_users
- (sub_server_id, user_id, display_name, joined_at, last_seen, is_admin)
- VALUES (?, ?, ?, ?, ?, ?)"
- )
- .bind(&through.sub_server_id)
- .bind(&through.user_id)
- .bind(&through.display_username)
- .bind(&through.joined_at)
- .bind(&through.last_seen_at)
- .bind(&through.is_admin)
- .execute(&self.store.db)
- .await?;
-
- // Ajouter au cache
- self.store.sub_server_users.push(through.clone());
-
- Ok(through)
- }
-}
-
-impl LinkSubServerUserRepository {
- // getters (db)
- pub async fn db_all(&self) -> Result, sqlx::Error> {
- sqlx::query_as("SELECT * FROM sub_server_users")
- .fetch_all(&self.store.db)
- .await
- }
-}
diff --git a/src/store/repositories/message_repository.rs b/src/store/repositories/message_repository.rs
deleted file mode 100644
index 971da80..0000000
--- a/src/store/repositories/message_repository.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use std::sync::Arc;
-use sqlx::SqlitePool;
-use uuid::Uuid;
-use crate::store::models::message::Message;
-use crate::store::store_service::StoreService;
-
-#[derive(Clone)]
-pub struct MessageRepository {
- store: StoreService
-}
-
-impl MessageRepository {
- pub fn new(store: StoreService) -> Self {
- Self {
- store
- }
- }
-}
-
-impl MessageRepository {
- // getters (caches)
-}
-
-impl MessageRepository {
- // writers
- pub async fn create(&self, mut message: Message) -> Result, sqlx::Error> {
- sqlx::query(
- "INSERT INTO message (id, channel_id, user_id, content, created_at) VALUES (?, ?, ?, ?, ?)"
- )
- .bind(&message.id)
- .bind(&message.channel_id)
- .bind(&message.user_id)
- .bind(&message.content)
- .bind(&message.created_at)
- .execute(&self.store.db)
- .await?;
-
- // ajouter au cache
- let arc_message = Arc::new(message.clone());
-
- Ok(arc_message)
- }
-
- pub async fn save(&self, message: &Message) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE message SET content = ? WHERE id = ?"
- )
- .bind(&message.content)
- .bind(&message.id)
- .execute(&self.store.db)
- .await?;
- Ok(())
- }
-
- pub async fn delete(&self, id: Uuid) -> Result {
- let rows_affected = sqlx::query("DELETE FROM message WHERE id = ?")
- .bind(&id)
- .execute(&self.store.db)
- .await?
- .rows_affected();
-
- if rows_affected > 0 {
- Ok(true)
- } else {
- Ok(false)
- }
- }
-}
-
-impl MessageRepository {
- // getters (db)
- pub async fn db_all(&self) -> Result, sqlx::Error> {
- sqlx::query_as("SELECT * FROM message")
- .fetch_all(&self.store.db)
- .await
- }
-}
\ No newline at end of file
diff --git a/src/store/repositories/mod.rs b/src/store/repositories/mod.rs
deleted file mode 100644
index 4d49ef3..0000000
--- a/src/store/repositories/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-mod sub_server_repository;
-mod channel_repository;
-mod user_repository;
-mod message_repository;
-mod link_sub_server_user_repository;
-
-pub use sub_server_repository::*;
-pub use channel_repository::*;
-pub use user_repository::*;
-pub use message_repository::*;
-pub use link_sub_server_user_repository::*;
\ No newline at end of file
diff --git a/src/store/repositories/sub_server_repository.rs b/src/store/repositories/sub_server_repository.rs
deleted file mode 100644
index 4396367..0000000
--- a/src/store/repositories/sub_server_repository.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use std::sync::Arc;
-use sqlx::SqlitePool;
-use uuid::Uuid;
-use crate::store::models::Channel;
-use crate::store::models::sub_server::SubServer;
-use crate::store::store_service::StoreService;
-use crate::utils::shared_store::SharedArcMap;
-
-#[derive(Clone)]
-pub struct SubServerRepository {
- store: StoreService
-}
-
-impl SubServerRepository {
- pub fn new(store: StoreService) -> Self {
- Self {
- store
- }
- }
-}
-
-impl SubServerRepository {
- // getters
- pub async fn all(&self) -> Vec> {
- self.store.sub_servers.values().collect()
- }
-
- pub async fn get(&self, id: Uuid) -> Option> {
- self.store.sub_servers.get(&id)
- }
-}
-
-impl SubServerRepository {
- // writers
- pub async fn create(&self, mut sub_server: SubServer) -> Result, sqlx::Error> {
- sqlx::query(
- "INSERT INTO sub_server (id, name, password, created_at) VALUES (?, ?, ?, ?)"
- )
- .bind(&sub_server.id)
- .bind(&sub_server.name)
- .bind(&sub_server.password)
- .bind(&sub_server.created_at)
- .execute(&self.store.db)
- .await?;
-
- // ajouter au cache
- let arc_server = Arc::new(sub_server.clone());
- self.store.sub_servers.insert_arc(sub_server.id, arc_server.clone());
-
- Ok(arc_server)
- }
-
- pub async fn save(&self, sub_server: &SubServer) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE sub_server SET name = ?, password = ? WHERE id = ?"
- )
- .bind(&sub_server.name)
- .bind(&sub_server.password)
- .bind(&sub_server.id)
- .execute(&self.store.db)
- .await?;
-
- // Mettre à jour le cache
- self.store.sub_servers.insert(sub_server.id, sub_server.clone());
-
- Ok(())
- }
-
- pub async fn delete(&self, id: Uuid) -> Result {
- let rows_affected = sqlx::query("DELETE FROM sub_server WHERE id = ?")
- .bind(&id)
- .execute(&self.store.db)
- .await?
- .rows_affected();
-
- if rows_affected > 0 {
- self.store.sub_servers.remove(&id);
- Ok(true)
- } else {
- Ok(false)
- }
- }
-}
-
-impl SubServerRepository {
- // getters (db)
- pub async fn db_all(&self) -> Result, sqlx::Error> {
- sqlx::query_as("SELECT * FROM sub_server")
- .fetch_all(&self.store.db)
- .await
- }
-}
\ No newline at end of file
diff --git a/src/store/repositories/user_repository.rs b/src/store/repositories/user_repository.rs
deleted file mode 100644
index 4241b5b..0000000
--- a/src/store/repositories/user_repository.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use std::sync::Arc;
-use sqlx::SqlitePool;
-use uuid::Uuid;
-use crate::store::models::user::User;
-use crate::store::store_service::StoreService;
-use crate::utils::shared_store::SharedArcMap;
-
-#[derive(Clone)]
-pub struct UserRepository {
- store: StoreService,
-}
-
-impl UserRepository {
- pub fn new(store: StoreService) -> Self {
- Self {
- store
- }
- }
-}
-
-impl UserRepository {
- // getters
- pub async fn all(&self) -> Vec> {
- self.store.users.values().collect()
- }
-
- pub async fn get(&self, id: Uuid) -> Option> {
- self.store.users.get(&id)
- }
-}
-
-impl UserRepository {
- // writers
- pub async fn create(&self, mut user: User) -> Result, sqlx::Error> {
- sqlx::query(
- "INSERT INTO user (id, name, password, created_at) VALUES (?, ?, ?, ?)"
- )
- .bind(&user.id)
- .bind(&user.username)
- .bind(&user.pub_key)
- .bind(&user.created_at)
- .execute(&self.store.db)
- .await?;
-
- // ajouter au cache
- let arc_server = Arc::new(user.clone());
- self.store.users.insert_arc(user.id, arc_server.clone());
-
- Ok(arc_server)
- }
-
- pub async fn save(&self, user: &User) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE user SET name = ?, password = ? WHERE id = ?"
- )
- .bind(&user.username)
- .bind(&user.pub_key)
- .bind(&user.id)
- .execute(&self.store.db)
- .await?;
-
- // Mettre à jour le cache
- self.store.users.insert(user.id, user.clone());
-
- Ok(())
- }
-
- pub async fn delete(&self, id: Uuid) -> Result {
- let rows_affected = sqlx::query("DELETE FROM user WHERE id = ?")
- .bind(&id)
- .execute(&self.store.db)
- .await?
- .rows_affected();
-
- if rows_affected > 0 {
- self.store.users.remove(&id);
- Ok(true)
- } else {
- Ok(false)
- }
- }
-}
-
-impl UserRepository {
- // getters (db)
- pub async fn db_all(&self) -> Result, sqlx::Error> {
- sqlx::query_as("SELECT * FROM user")
- .fetch_all(&self.store.db)
- .await
- }
-}
\ No newline at end of file
diff --git a/src/store/session/client.rs b/src/store/session/client.rs
deleted file mode 100644
index a4800ab..0000000
--- a/src/store/session/client.rs
+++ /dev/null
@@ -1,213 +0,0 @@
-
-use std::collections::{HashMap, HashSet};
-use std::hash::{Hash, Hasher};
-use std::net::SocketAddr;
-use std::sync::{Arc, Weak};
-use std::time::Instant;
-use axum::extract::ws::WebSocket;
-use uuid::Uuid;
-use crate::store::models::{Channel, SubServer};
-use crate::store::models::user::User;
-use crate::utils::shared_store::{SharedArcHashSet, SharedArcMap, SharedArcVec};
-
-/// Représente un client connecté au serveur (WebSocket + optionnel UDP)
-///
-/// Chaque client a une connexion WebSocket obligatoire et peut avoir une connexion UDP
-/// pour la voix s'il rejoint un channel vocal.
-#[derive(Debug)]
-pub struct Client {
- // ===== CHAMPS IMMUTABLES =====
- pub session_id: Uuid,
- pub user: User,
- pub websocket: WebSocket,
- pub created_at: Instant,
-
- // ===== CHAMPS MUTABLES =====
- pub udp_socket: Option,
- pub voice_channel: Option,
-
- // ===== RÉFÉRENCES INTERNES =====
- /// Référence faible vers le manager pour l'auto-nettoyage
- manager: Option>,
-}
-
-impl Client {
- /// Crée un nouveau client
- pub fn new(user: User, websocket: WebSocket) -> Self {
- Self {
- session_id: Uuid::new_v4(),
- user,
- websocket,
- created_at: Instant::now(),
- udp_socket: None,
- voice_channel: None,
- manager: None,
- }
- }
-
- // ===== MÉTHODES MUTABLES INTERNES =====
- // Ces méthodes sont pub(crate) - elles ne doivent être appelées que via ClientManager::modify_client()
-
- /// Configure l'adresse UDP (méthode interne)
- pub(crate) fn set_udp_socket(&mut self, udp_socket: SocketAddr) {
- self.udp_socket = Some(udp_socket);
- }
-
- /// Supprime l'adresse UDP (méthode interne)
- pub(crate) fn remove_udp_socket(&mut self) {
- self.udp_socket = None;
- }
-
- /// Configure la référence vers le manager (appelé par le manager)
- pub(crate) fn set_manager(&mut self, manager: Weak) {
- self.manager = Some(manager);
- }
-}
-
-impl PartialEq for Client {
- fn eq(&self, other: &Self) -> bool {
- self.session_id == other.session_id
- }
-}
-
-impl Eq for Client {}
-
-impl Hash for Client {
- fn hash(&self, state: &mut H) {
- self.session_id.hash(state);
- }
-}
-
-/// Nettoyage automatique quand le client est détruit
-impl Drop for Client {
- fn drop(&mut self) {
- if let Some(manager) = self.manager.as_ref().and_then(|w| w.upgrade()) {
- manager.cleanup_client_index(&self);
- }
- }
-}
-
-// =========================================================================
-// CLIENT MANAGER
-// =========================================================================
-
-/// Gestionnaire centralisé de tous les clients connectés
-///
-/// Maintient les clients et leurs index pour des recherches efficaces.
-/// Thread-safe via SharedArcMap.
-pub struct ClientManager {
- /// Map principale des clients par session_id
- clients: SharedArcMap,
-
- // ===== INDEX POUR RECHERCHES RAPIDES =====
- /// Index des clients par channel_id
- voice_channel_clients: SharedArcMap>, // channel_id -> HashSet
- /// Index des clients par sub_server_id
- sub_server_clients: SharedArcMap>, // sub_server_id -> Vec
- /// Index des clients par adresse UDP
- udp_clients: SharedArcMap, // udp_address -> session_id
-}
-
-impl ClientManager {
- /// Crée un nouveau gestionnaire de clients
- pub fn new() -> Self {
- Self {
- clients: SharedArcMap::new(),
- voice_channel_clients: SharedArcMap::new(),
- sub_server_clients: SharedArcMap::new(),
- udp_clients: SharedArcMap::new(),
- }
- }
-
- // ===== GESTION DES CLIENTS =====
-
- /// Ajoute un nouveau client au gestionnaire
- pub fn add_client(self: &Arc, mut client: Client) -> Arc {
- let session_id = client.session_id.clone();
-
- // Lier le client au manager pour l'auto-nettoyage
- client.set_manager(Arc::downgrade(self));
-
- // Stocker et retourner
- let arc_client = Arc::new(client);
- self.clients.insert_arc(session_id, arc_client.clone());
-
- arc_client
- }
-
- pub fn join_sub_server(&self, client: &Arc, sub_server: &SubServer) {
- let clients = self.sub_server_clients.get(sub_server).unwrap_or_else(|| {
- let new_clients = Arc::new(SharedArcHashSet::new());
- self.sub_server_clients.insert_arc(sub_server.clone(), new_clients.clone());
- new_clients
- });
-
- clients.insert_arc(client.clone());
- }
-
- /// Obtient un client par son session_id
- pub fn get_client(&self, session_id: &Uuid) -> Option> {
- self.clients.get(session_id)
- }
-
- /// Obtient tous les clients connectés
- pub fn get_all_clients(&self) -> Vec> {
- self.clients.values().collect()
- }
-
- // ===== OPÉRATIONS DE MODIFICATION =====
-
- /// Modifie un client via une closure thread-safe
- ///
- /// Cette méthode garantit que la modification est atomique et que
- /// les index restent cohérents avec l'état du client.
- ///
- /// # Arguments
- /// * `session_id` - L'ID de session du client à modifier
- /// * `f` - La closure qui recevra une référence mutable vers le client
- ///
- /// # Returns
- /// `true` si le client a été trouvé et modifié, `false` sinon
- pub fn modify_client(&self, client: &Client, f: F) -> bool
- where
- F: FnOnce(&mut Client),
- {
- // Convertir &str en String pour les clés
-
- todo!()
- }
-}
-
-impl ClientManager {
- // Delete methods
- /// Supprime tous les index d'un client
- pub fn cleanup_client_index(&self, client: &Client) {
- if let Some(channel) = client.voice_channel.as_ref() {
- self.remove_client_from_voice_channel(channel, client);
- }
- }
-
- /// Supprime un client du gestionnaire
- pub fn remove_client(&self, session_id: &Uuid) {
- // Convertir &str en String pour les clés
- if let Some(client) = self.clients.get(session_id) {
- // Nettoyer tous les index
- self.cleanup_client_index(&client);
-
- // Supprimer le client de la liste principale
- self.clients.remove(&session_id);
- }
- }
-
- pub fn remove_client_from_voice_channel(&self, voice_channel: &Channel, client: &Client) {
- if let Some(channel) = self.voice_channel_clients.get(voice_channel) {
- channel.remove(client);
- }
- }
-
- pub fn remove_client_from_sub_servers(&self, client: &Client) {
- for (sub_server, clients) in self.sub_server_clients.iter() {
- clients.remove(client);
- }
- }
-}
\ No newline at end of file
diff --git a/src/store/session/mod.rs b/src/store/session/mod.rs
deleted file mode 100644
index 2322d1e..0000000
--- a/src/store/session/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-mod client;
\ No newline at end of file
diff --git a/src/store/store_service.rs b/src/store/store_service.rs
deleted file mode 100644
index 4fda879..0000000
--- a/src/store/store_service.rs
+++ /dev/null
@@ -1,125 +0,0 @@
-use std::collections::HashMap;
-use std::sync::Arc;
-use uuid::Uuid;
-use sqlx::{AnyPool, SqlitePool};
-use crate::store::models::{SubServer, Channel, User, Message, ChannelType, LinkSubServerUser};
-use crate::store::repositories::{
- SubServerRepository, ChannelRepository, UserRepository,
- MessageRepository, LinkSubServerUserRepository
-};
-use crate::utils::shared_store::{SharedArcMap, SharedArcVec};
-
-#[derive(Debug, Clone)]
-pub enum StoreEvent {
- SubServerCreated(Arc),
- SubServerUpdated(Arc),
- ChannelCreated(Arc),
- ChannelUpdated(Arc),
- MessageSent(Message),
- UserJoinedSubServer { sub_server_id: Uuid, user_id: Uuid },
-}
-
-#[derive(Clone)]
-pub struct StoreService {
- // Database
- pub db: SqlitePool,
-
- // ✅ Caches mémoire centralisés
- pub users: SharedArcMap,
- pub sub_servers: SharedArcMap,
- pub channels: SharedArcMap,
- pub sub_server_users: SharedArcVec,
-}
-
-
-impl StoreService {
- pub async fn new(database_url: &str) -> Result {
- let connection_url = Self::normalize_database_url(database_url);
- println!("🔍 Tentative de connexion à: {}", connection_url);
-
- let db = SqlitePool::connect(&connection_url).await?;
-
- // sqlx::migrate!("./src/store/migrations").run(&db).await?;
-
- let mut service = Self {
- db,
- users: SharedArcMap::new(),
- sub_servers: SharedArcMap::new(),
- channels: SharedArcMap::new(),
- sub_server_users: SharedArcVec::new(),
- };
-
- // Charger tout en mémoire au démarrage
- let _ = service.load_all_caches().await;
-
- Ok(service)
- }
-
- async fn load_all_caches(&self) -> Result<(), sqlx::Error> {
- let sub_server_rep = SubServerRepository::new(self.clone());
- let user_rep = UserRepository::new(self.clone());
- let channel_rep = ChannelRepository::new(self.clone());
- let link_sub_server_user_rep = LinkSubServerUserRepository::new(self.clone());
-
- // sub_servers
- let sub_servers = sub_server_rep.db_all().await?;
- self.sub_servers.insert_batch_with_key(sub_servers, |sub_server| sub_server.id);
-
- // Users
- let users = user_rep.db_all().await?;
- self.users.insert_batch_with_key(users, |user| user.id);
-
- // Channels
- let channels = channel_rep.db_all().await?;
- self.channels.insert_batch_with_key(channels, |channel| channel.id);
-
- // Relations N-N
- let relations: Vec = link_sub_server_user_rep.db_all().await?;
- self.sub_server_users.extend(relations);
-
- Ok(())
- }
-
-}
-
-impl StoreService {
- // Getters repositories
- pub fn user_repository(&self) -> UserRepository {
- UserRepository::new(self.clone())
- }
-
- pub fn sub_server_repository(&self) -> SubServerRepository {
- SubServerRepository::new(self.clone())
- }
-
- pub fn channel_repository(&self) -> ChannelRepository {
- ChannelRepository::new(self.clone())
- }
-
- pub fn message_repository(&self) -> MessageRepository {
- MessageRepository::new(self.clone())
- }
-
- pub fn sub_server_user_repository(&self) -> LinkSubServerUserRepository {
- LinkSubServerUserRepository::new(self.clone())
- }
-
-}
-
-impl StoreService {
- // ===== HELPERS =====
- /// ✅ Normalise l'URL pour supporter différentes bases de données
- fn normalize_database_url(database_url: &str) -> String {
- // Si c'est déjà une URL complète, on la retourne telle quelle
- if database_url.contains("://") {
- return database_url.to_string();
- }
-
- // Sinon, on assume que c'est SQLite (comportement actuel)
- if database_url.starts_with("sqlite:") {
- database_url.to_string()
- } else {
- format!("sqlite:{}?mode=rwc", database_url)
- }
- }
-}
diff --git a/src/utils/config.rs b/src/utils/config.rs
new file mode 100644
index 0000000..8048799
--- /dev/null
+++ b/src/utils/config.rs
@@ -0,0 +1,116 @@
+use std::env;
+use std::path::Path;
+use serde::Deserialize;
+use crate::utils::logger::ContextLogger;
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Config {
+ #[serde(default = "default_server_host")]
+ pub server_host: String,
+
+ #[serde(default = "default_server_port")]
+ pub server_port: u16,
+
+ #[serde(default = "default_buffer_size")]
+ pub buffer_size: usize,
+
+ #[serde(default = "default_log_level")]
+ pub log_level: String,
+
+ #[serde(default)]
+ pub debug_mode: bool,
+
+ pub workers: Option,
+ pub database_url: Option,
+}
+
+// Default values
+fn default_server_host() -> String { "127.0.0.1".to_string() }
+fn default_server_port() -> u16 { 8080 }
+fn default_buffer_size() -> usize { 1024 }
+fn default_log_level() -> String { "info".to_string() }
+
+#[derive(Debug)]
+pub enum ConfigError {
+ EnvFile(dotenvy::Error),
+ Parse(envy::Error),
+}
+
+impl std::fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ConfigError::EnvFile(e) => write!(f, "Error loading .env file: {}", e),
+ ConfigError::Parse(e) => write!(f, "Error parsing configuration: {}", e),
+ }
+ }
+}
+
+impl std::error::Error for ConfigError {}
+
+impl Config {
+ pub fn load() -> Result {
+ let logger = ContextLogger::new("CONFIG");
+
+ // Display current working directory
+ match env::current_dir() {
+ Ok(path) => {
+ logger.info(&format!("Working directory: {}", path.display()));
+ }
+ Err(e) => {
+ logger.error(&format!("Error getting directory: {}", e));
+ }
+ }
+
+ // Check if .env file exists and load it
+ let env_path = Path::new(".env");
+ if env_path.exists() {
+ logger.info(".env found");
+
+ // Load the .env file
+ match dotenvy::from_path(env_path) {
+ Ok(_) => logger.info(".env file loaded successfully"),
+ Err(e) => {
+ logger.error(&format!("Error loading .env: {}", e));
+ return Err(ConfigError::EnvFile(e));
+ }
+ }
+ } else {
+ logger.info(".env file does not exist - using defaults and system env vars");
+ }
+
+ // Load configuration from environment variables
+ logger.info("Loading configuration from environment variables");
+
+ let config = envy::from_env::().map_err(ConfigError::Parse)?;
+
+ logger.info("Configuration loaded successfully");
+ config.print_summary();
+
+ Ok(config)
+ }
+
+ pub fn socket_addr(&self) -> String {
+ format!("{}:{}", self.server_host, self.server_port)
+ }
+
+ pub fn workers_count(&self) -> usize {
+ self.workers.unwrap_or_else(|| {
+ crate::utils::toolbox::number_of_cpus()
+ })
+ }
+
+ pub fn print_summary(&self) {
+ let logger = ContextLogger::new("CONFIG");
+ logger.info(&format!("Server address: {}", self.socket_addr()));
+ logger.info(&format!("Workers: {}", self.workers_count()));
+ logger.info(&format!("Buffer size: {} bytes", self.buffer_size));
+ logger.info(&format!("Log level: {}", self.log_level));
+ logger.info(&format!("Debug mode: {}", self.debug_mode));
+
+ if self.database_url.is_some() {
+ logger.info("Database URL: [CONFIGURED]");
+ } else {
+ logger.info("Database URL: [NOT SET]");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/utils/logger.rs b/src/utils/logger.rs
new file mode 100644
index 0000000..3c376de
--- /dev/null
+++ b/src/utils/logger.rs
@@ -0,0 +1,88 @@
+use log::{info, warn, error, debug, LevelFilter};
+use std::io::Write;
+
+#[derive(Clone)]
+pub struct ContextLogger {
+ context: String,
+}
+
+impl ContextLogger {
+ pub fn new(context: &str) -> Self {
+ Self {
+ context: context.to_string(),
+ }
+ }
+
+ pub fn with_sub_context(&self, sub_context: &str) -> Self {
+ Self {
+ context: format!("{}:{}", self.context, sub_context),
+ }
+ }
+
+ pub fn info(&self, msg: &str) {
+ info!("[{}] {}", self.context, msg);
+ }
+
+ pub fn warn(&self, msg: &str) {
+ warn!("[{}] {}", self.context, msg);
+ }
+
+ pub fn error(&self, msg: &str) {
+ error!("[{}] {}", self.context, msg);
+ }
+
+ pub fn debug(&self, msg: &str) {
+ debug!("[{}] {}", self.context, msg);
+ }
+}
+
+// Macros pour simplifier l'usage avec formatage
+#[macro_export]
+macro_rules! log_info {
+ ($logger:expr, $($arg:tt)*) => {
+ log::info!("[{}] {}", $logger.context, format_args!($($arg)*));
+ };
+}
+
+#[macro_export]
+macro_rules! log_warn {
+ ($logger:expr, $($arg:tt)*) => {
+ log::warn!("[{}] {}", $logger.context, format_args!($($arg)*));
+ };
+}
+
+#[macro_export]
+macro_rules! log_error {
+ ($logger:expr, $($arg:tt)*) => {
+ log::error!("[{}] {}", $logger.context, format_args!($($arg)*));
+ };
+}
+
+#[macro_export]
+macro_rules! log_debug {
+ ($logger:expr, $($arg:tt)*) => {
+ log::debug!("[{}] {}", $logger.context, format_args!($($arg)*));
+ };
+}
+
+pub fn init_logger(log_level: &str) {
+ let level = match log_level.to_lowercase().as_str() {
+ "debug" => LevelFilter::Debug,
+ "info" => LevelFilter::Info,
+ "warn" | "warning" => LevelFilter::Warn,
+ "error" => LevelFilter::Error,
+ _ => LevelFilter::Info,
+ };
+
+ env_logger::Builder::from_default_env()
+ .filter_level(level)
+ .format_timestamp_secs()
+ .format(|buf, record| {
+ writeln!(buf, "{} | {} | {}",
+ buf.timestamp(),
+ record.level(),
+ record.args()
+ )
+ })
+ .init();
+}
\ No newline at end of file
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index cf747ee..79b0ed8 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1 +1,4 @@
-pub mod shared_store;
\ No newline at end of file
+pub mod shared_store;
+pub mod config;
+pub mod toolbox;
+pub mod logger;
\ No newline at end of file
diff --git a/src/utils/toolbox.rs b/src/utils/toolbox.rs
new file mode 100644
index 0000000..4c56551
--- /dev/null
+++ b/src/utils/toolbox.rs
@@ -0,0 +1,9 @@
+pub fn number_of_cpus() -> usize {
+ match std::thread::available_parallelism() {
+ Ok(n) => n.get(),
+ Err(_) => {
+ eprintln!("Warning: Could not determine number of CPUs, defaulting to 1");
+ 1
+ }
+ }
+}
\ No newline at end of file