From 980c5884a498b8e238cfd847e43b8060d92e6190 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sun, 3 Nov 2024 10:13:31 +0100 Subject: [PATCH] Replace Rusqlite by Sqlx and Actix by Axum (A lot of changes) --- .cargo/config.toml | 2 +- Cargo.lock | 2775 +++++++++++------ Cargo.toml | 8 +- README.md | 1 - TODO.md | 21 +- backend/Cargo.toml | 58 +- backend/src/config.rs | 47 +- backend/src/consts.rs | 10 +- backend/src/data/asynchronous.rs | 195 -- backend/src/data/db.rs | 910 +++--- backend/src/data/mod.rs | 2 +- backend/src/data/utils.rs | 33 + backend/src/email.rs | 17 +- backend/src/main.rs | 170 +- backend/src/model.rs | 1 + backend/src/services.rs | 709 ++--- backend/src/services/webapi.rs | 64 +- backend/src/utils.rs | 29 +- backend/templates/base_with_header.html | 2 +- backend/templates/message.html | 5 +- ...ge_base.html => message_without_user.html} | 4 +- backend/templates/title.html | 2 +- check_cargo_dependencies_upgrade.nu | 2 + common/src/lib.rs | 2 +- common/src/ron_api.rs | 1 - common/src/utils.rs | 21 +- deploy.nu | 9 +- frontend/Cargo.toml | 16 +- 28 files changed, 2857 insertions(+), 2259 deletions(-) delete mode 100644 backend/src/data/asynchronous.rs create mode 100644 backend/src/data/utils.rs rename backend/templates/{message_base.html => message_without_user.html} (53%) create mode 100644 check_cargo_dependencies_upgrade.nu diff --git a/.cargo/config.toml b/.cargo/config.toml index 73227e6..c0b281c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc.exe" \ No newline at end of file +linker = "aarch64-linux-gnu-gcc.exe" diff --git a/Cargo.lock b/Cargo.lock index 38f2ad2..84749ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,261 +3,52 @@ version = 3 [[package]] -name = "actix-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "actix-files" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" -dependencies = [ - "actix-http", - "actix-service", - "actix-utils", - "actix-web", - "askama_escape", - "bitflags", - "bytes", - "derive_more", - "futures-core", - "http-range", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", -] - -[[package]] -name = "actix-http" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash 0.8.3", - "base64 0.21.0", - "bitflags", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.3" +name = "addr2line" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "quote", - "syn 1.0.109", + "gimli", ] [[package]] -name = "actix-router" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" -dependencies = [ - "bytestring", - "http", - "regex", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "num_cpus", - "socket2", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-utils", - "actix-web-codegen", - "ahash 0.7.6", - "bytes", - "bytestring", - "cfg-if 1.0.0", - "cookie", - "derive_more", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2", - "time 0.3.20", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.7.6" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if 1.0.0", - "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "allocator-api2" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] -name = "alloc-stdlib" -version = "0.2.2" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" @@ -270,60 +61,70 @@ dependencies = [ [[package]] name = "anstream" -version = "0.2.6" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", - "is-terminal", + "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "anstyle-wincon" -version = "0.2.0" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.59.0", ] [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", + "cpufeatures", "password-hash", ] [[package]] name = "askama" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" dependencies = [ "askama_derive", "askama_escape", @@ -334,29 +135,30 @@ dependencies = [ ] [[package]] -name = "askama_actix" -version = "0.14.0" +name = "askama_axum" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55" +checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163" dependencies = [ - "actix-web", "askama", + "axum-core", + "http", ] [[package]] name = "askama_derive" -version = "0.12.1" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" dependencies = [ + "askama_parser", "basic-toml", "mime", "mime_guess", - "nom", "proc-macro2", "quote", "serde", - "syn 2.0.13", + "syn", ] [[package]] @@ -365,23 +167,157 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "serde", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -391,18 +327,21 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" dependencies = [ "serde", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "blake2" @@ -422,55 +361,31 @@ dependencies = [ "generic-array", ] -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "bytes" -version = "1.4.0" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bytestring" -version = "1.3.0" +name = "bytes" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" -dependencies = [ - "bytes", -] +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.0.79" +version = "1.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" dependencies = [ - "jobserver", + "shlex", ] [[package]] @@ -487,70 +402,73 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets 0.52.6", +] + +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.5", + "stacker", ] [[package]] name = "clap" -version = "4.2.1" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.2.1" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn", ] [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "colorchoice" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "common" @@ -564,15 +482,13 @@ dependencies = [ [[package]] name = "comrak" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784836d0812dade01579cc0cc9b1684847044e716fd7aa6bffbc172e42199500" +checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894" dependencies = [ "entities", "memchr", "once_cell", - "pest", - "pest_derive", "regex", "slug", "typed-arena", @@ -580,18 +496,12 @@ dependencies = [ ] [[package]] -name = "concolor-override" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "windows-sys 0.45.0", + "crossbeam-utils", ] [[package]] @@ -604,47 +514,77 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "cookie" -version = "0.16.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", - "time 0.3.20", + "time", "version_check", ] [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "crc" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ - "cfg-if 1.0.0", + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -656,109 +596,106 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.94" +name = "der" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "cxx-build" -version = "1.0.94" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.13", + "powerfmt", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" +name = "derive_more" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", + "derive_more-impl", ] [[package]] -name = "derive_more" -version = "0.99.17" +name = "derive_more-impl" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 1.0.109", + "syn", + "unicode-xid", ] [[package]] name = "deunicode" -version = "0.4.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "either" -version = "1.8.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "email-encoding" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "memchr", ] [[package]] name = "email_address" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" - -[[package]] -name = "encoding_rs" -version = "0.8.32" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "entities" @@ -767,68 +704,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] -name = "env_logger" -version = "0.10.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.0" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cc", - "libc", + "cfg-if 1.0.0", + "home", + "windows-sys 0.48.0", ] [[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "event-listener" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] -name = "flate2" -version = "1.0.25" +name = "flume" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ - "crc32fast", - "miniz_oxide", + "futures-core", + "futures-sink", + "spin", ] [[package]] @@ -839,9 +766,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -857,26 +784,11 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -884,15 +796,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -900,44 +812,42 @@ dependencies = [ ] [[package]] -name = "futures-io" -version = "0.3.28" +name = "futures-intrusive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] [[package]] -name = "futures-macro" -version = "0.3.28" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", @@ -958,291 +868,458 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "windows", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", ] [[package]] -name = "h2" -version = "0.3.16" +name = "hyper-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", - "fnv", - "futures-core", - "futures-sink", "futures-util", "http", - "indexmap", - "slab", + "http-body", + "hyper", + "pin-project-lite", "tokio", - "tokio-util", - "tracing", + "tower-service", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "iana-time-zone" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ - "ahash 0.7.6", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "hashlink" -version = "0.8.1" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "hashbrown", + "cc", ] [[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.2.6" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "libc", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "hostname" -version = "0.3.1" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "libc", - "match_cfg", - "winapi", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "http" -version = "0.2.9" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "bytes", - "fnv", - "itoa", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "http-range" -version = "0.1.5" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "httparse" -version = "1.8.0" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] [[package]] -name = "httpdate" -version = "1.0.2" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] -name = "humansize" -version = "2.1.3" +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "libm", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "humantime" -version = "2.1.0" +name = "icu_properties_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] -name = "iana-time-zone" -version = "0.1.56" +name = "icu_provider" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "cxx", - "cxx-build", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.12" +name = "idna" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" dependencies = [ - "cfg-if 1.0.0", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] -name = "io-lifetimes" -version = "1.0.10" +name = "indexmap" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown 0.15.0", ] [[package]] -name = "is-terminal" -version = "0.4.7" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "jobserver" -version = "0.1.26" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lettre" -version = "0.10.4" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" +checksum = "0161e452348e399deb685ba05e55ee116cae9410f4f51fe42d597361444521d9" dependencies = [ - "base64 0.21.0", + "async-trait", + "base64 0.22.1", + "chumsky", "email-encoding", "email_address", "fastrand", + "futures-io", "futures-util", "hostname", "httpdate", - "idna", + "idna 1.0.2", "mime", "nom", - "once_cell", + "percent-encoding", "quoted_printable", "rustls", "rustls-pemfile", + "rustls-pki-types", "socket2", "tokio", + "tokio-rustls", + "url", "webpki-roots", ] [[package]] name = "libc" -version = "0.2.141" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" -version = "0.25.2" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1250,68 +1327,63 @@ dependencies = [ ] [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "linux-raw-sys" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "linux-raw-sys" -version = "0.3.1" +name = "litemap" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] -name = "local-channel" -version = "0.1.3" +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", + "autocfg", + "scopeguard", ] [[package]] -name = "local-waker" -version = "0.1.3" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "lock_api" -version = "0.4.9" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "autocfg", - "scopeguard", + "regex-automata 0.1.10", ] [[package]] -name = "log" -version = "0.4.17" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "match_cfg" -version = "0.1.0" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.0", + "digest", +] [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memory_units" @@ -1327,9 +1399,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1343,23 +1415,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.6" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "wasi", + "windows-sys 0.52.0", ] [[package]] @@ -1372,46 +1444,101 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] -name = "num_cpus" -version = "1.15.0" +name = "object" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ - "hermit-abi 0.2.6", - "libc", + "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1419,15 +1546,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.6", ] [[package]] @@ -1443,128 +1570,111 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] [[package]] -name = "pest" -version = "2.5.7" +name = "percent-encoding" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" -dependencies = [ - "thiserror", - "ucd-trie", -] +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pest_derive" -version = "2.5.7" +name = "pin-project-lite" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" -dependencies = [ - "pest", - "pest_generator", -] +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] -name = "pest_generator" -version = "2.5.7" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.13", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pest_meta" -version = "2.5.7" +name = "pkcs1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "once_cell", - "pest", - "sha2", + "der", + "pkcs8", + "spki", ] [[package]] -name = "pin-project-lite" -version = "0.2.9" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] -name = "pkg-config" -version = "0.3.26" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.26" +name = "psm" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ - "proc-macro2", + "cc", ] [[package]] -name = "quoted_printable" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24039f627d8285853cc90dcddf8c1ebfaa91f834566948872b225b9a28ed1b6" - -[[package]] -name = "r2d2" -version = "0.8.10" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", + "proc-macro2", ] [[package]] -name = "r2d2_sqlite" -version = "0.21.0" +name = "quoted_printable" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f5d0337e99cd5cacd91ffc326c6cc9d8078def459df560c4f9bf9ba4a51034" -dependencies = [ - "r2d2", - "rusqlite", -] +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "rand" @@ -1600,47 +1710,69 @@ dependencies = [ name = "recipes" version = "1.0.0" dependencies = [ - "actix-files", - "actix-web", "argon2", "askama", - "askama_actix", + "askama_axum", + "axum", + "axum-extra", "chrono", "clap", "common", "derive_more", - "env_logger", - "futures", "itertools", "lettre", - "log", - "r2d2", - "r2d2_sqlite", "rand", "rand_core", "ron", - "rusqlite", "serde", + "sqlx", + "thiserror", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "regex-automata" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "bitflags", + "regex-syntax 0.6.29", ] [[package]] -name = "regex" -version = "1.7.3" +name = "regex-automata" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] @@ -1649,175 +1781,179 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if 1.0.0", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "ron" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "bitflags", "serde", + "serde_derive", ] [[package]] -name = "rusqlite" -version = "0.28.0" +name = "rsa" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ - "bitflags", - "chrono", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.37.8" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aef160324be24d31a62147fae491c14d2204a3865c7ca8c3b0d7f7bcb3ea635" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.0" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07180898a28ed6a7f7ba2311594308f595e3dd2e3c3812fa0a80a47b45f17e5d" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "log", + "once_cell", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.0", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] -name = "ryu" -version = "1.0.13" +name = "rustversion" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] -name = "scheduled-thread-pool" -version = "0.2.7" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" -dependencies = [ - "parking_lot", -] +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "1.0.17" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1832,9 +1968,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1843,81 +1979,361 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "slug" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", + "wasm-bindgen", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "stringprep" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1925,63 +2341,81 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.13" +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "tempfile" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ - "winapi-util", + "cfg-if 1.0.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn", ] [[package]] -name = "time" -version = "0.1.45" +name = "thread_local" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "cfg-if 1.0.0", + "once_cell", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -1989,24 +2423,35 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2019,11 +2464,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -2031,42 +2476,169 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "windows-sys 0.45.0", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", "tracing", ] +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if 1.0.0", "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", + "nu-ansi-term", "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2077,51 +2649,54 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.5" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] -name = "unicode-width" -version = "0.1.10" +name = "unicode-properties" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -2131,26 +2706,44 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" @@ -2160,52 +2753,53 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if 1.0.0", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2213,28 +2807,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2242,11 +2836,11 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.0" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ - "rustls-webpki", + "rustls-pki-types", ] [[package]] @@ -2261,6 +2855,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2278,187 +2882,280 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "winapi", + "windows-core", + "windows-targets 0.52.6", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "windows" +name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.52.6", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[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_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "zstd" -version = "0.12.3+zstd.1.5.2" +name = "yoke" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" dependencies = [ - "zstd-safe", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "zstd-safe" -version = "6.0.5+zstd.1.5.4" +name = "yoke-derive" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ - "libc", - "zstd-sys", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "cc", - "libc", - "pkg-config", + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 1686e30..ffc5e91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,8 @@ [workspace] -members = [ - "backend", - "frontend", - "common", -] +resolver = "2" + +members = ["backend", "frontend", "common"] [profile.release] strip = true diff --git a/README.md b/README.md index cb02d2c..338aba4 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ To launch node run 'npm run start' in 'frontend/www' directory ## Useful URLs * Rust patterns : https://github.com/rust-unofficial/patterns/tree/master/patterns -* Rusqlite (SQLite) : https://docs.rs/rusqlite/0.20.0/rusqlite/ * Node install: https://nodejs.org/en/download/ diff --git a/TODO.md b/TODO.md index 7fb8595..b3186cc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,20 +1,29 @@ +* Clean the old code + commit +* How to log error to journalctl or elsewhere + debug log? * Try using WASM for all the client logic (test on editing/creating a recipe) * Understand the example here: * https://github.com/rustwasm/wasm-bindgen/tree/main/examples/todomvc -> https://rustwasm.github.io/wasm-bindgen/exbuild/todomvc/#/ -* Describe the use cases. -* Define the UI (mockups). - * Two CSS: one for desktop and one for mobile - * Use CSS flex/grid to define a good design/layout -* Define the logic behind each page and action. +* Add a level of severity for the message template, use error severity in "impl axum::response::IntoResponse for db::DBError" +* Review the recipe model (SQL) +* Describe the use cases in details. + * Define the UI (mockups). + * Two CSS: one for desktop and one for mobile + * Use CSS flex/grid to define a good design/layout + * Define the logic behind each page and action. +* Implement: + .service(services::edit_recipe) + .service(services::new_recipe) + .service(services::webapi::set_recipe_title) + .service(services::webapi::set_recipe_description) * Add support to translations into db model. +[ok] Reactivate sign up/in/out [ok] Change all id to i64 [ok] Check cookie lifetime -> Session by default [ok] Asynchonous email sending and database requests [ok] Try to return Result for async routes (and watch what is printed in log) [ok] Then try to make async database calls [ok] Set email sending as async and show a waiter when sending email. Handle (and test) a timeout (~10s). -> (timeout put to 60s) -[ok] How to log error to journalctl? [ok] Sign out [ok] Read all the askama doc and see if the current approach is good [ok] Handle 404 diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d78c385..a6c5bdf 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,36 +5,48 @@ authors = ["Grégory Burri "] edition = "2021" [dependencies] -common = {path = "../common"} +common = { path = "../common" } -actix-web = "4" -actix-files = "0.6" +axum = { version = "0.7", features = ["macros"] } +axum-extra = { version = "0.9", features = ["cookie"] } +tokio = { version = "1", features = ["full"] } +tower = { version = "0.5", features = ["util"] } +tower-http = { version = "0.6", features = ["fs", "trace"] } -chrono = "0.4" - -ron = "0.8" # Rust object notation, to load configuration files. -serde = {version = "1.0", features = ["derive"]} - -itertools = "0.10" -clap = {version = "4", features = ["derive"]} +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } -log = "0.4" -env_logger = "0.10" +chrono = "0.4" -r2d2_sqlite = "0.21" # Connection pool with rusqlite (SQLite access). -r2d2 = "0.8" -rusqlite = {version = "0.28", features = ["bundled", "chrono"]} +# Rust object notation, to load configuration files. +ron = "0.8" +serde = { version = "1.0", features = ["derive"] } -futures = "0.3" # Needed by askam with the feature 'with-actix-web'. +itertools = "0.13" +clap = { version = "4", features = ["derive"] } -askama = {version = "0.12", features = ["with-actix-web", "mime", "mime_guess", "markdown"]} -askama_actix = "0.14" +sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] } -argon2 = {version = "0.5", features = ["default", "std"]} -rand_core = {version = "0.6", features = ["std"]} +askama = { version = "0.12", features = [ + "with-axum", + "mime", + "mime_guess", + "markdown", +] } +askama_axum = "0.4" +argon2 = { version = "0.5", features = ["default", "std"] } +rand_core = { version = "0.6", features = ["std"] } rand = "0.8" -lettre = {version = "0.10", default-features = false, features = ["smtp-transport", "pool", "hostname", "builder", "rustls-tls"]} - -derive_more = "0.99" \ No newline at end of file +lettre = { version = "0.11", default-features = false, features = [ + "smtp-transport", + "pool", + "hostname", + "builder", + "tokio1", + "tokio1-rustls-tls", +] } + +derive_more = { version = "1", features = ["full"] } +thiserror = "1" diff --git a/backend/src/config.rs b/backend/src/config.rs index 24af485..59ee30c 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,22 +1,38 @@ use std::{fmt, fs::File}; -use ron::de::from_reader; -use serde::Deserialize; +use ron::{ + de::from_reader, + ser::{to_writer_pretty, PrettyConfig}, +}; +use serde::{Deserialize, Serialize}; use crate::consts; -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Serialize, Clone)] pub struct Config { pub port: u16, + pub smtp_relay_address: String, pub smtp_login: String, pub smtp_password: String, } +impl Config { + pub fn default() -> Self { + Config { + port: 8082, + smtp_relay_address: "mail.something.com".to_string(), + smtp_login: "login".to_string(), + smtp_password: "password".to_string(), + } + } +} + // Avoid to print passwords. impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Config") .field("port", &self.port) + .field("smtp_relay_address", &self.smtp_relay_address) .field("smtp_login", &self.smtp_login) .field("smtp_password", &"*****") .finish() @@ -24,10 +40,25 @@ impl fmt::Debug for Config { } pub fn load() -> Config { - let f = File::open(consts::FILE_CONF) - .unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)); - match from_reader(f) { - Ok(c) => c, - Err(e) => panic!("Failed to load config: {}", e), + match File::open(consts::FILE_CONF) { + Ok(file) => from_reader(file).expect(&format!( + "Failed to open configuration file {}", + consts::FILE_CONF + )), + Err(_) => { + let file = File::create(consts::FILE_CONF).expect(&format!( + "Failed to create default configuration file {}", + consts::FILE_CONF + )); + + let default_config = Config::default(); + + to_writer_pretty(file, &default_config, PrettyConfig::new()).expect(&format!( + "Failed to write default configuration file {}", + consts::FILE_CONF + )); + + default_config + } } } diff --git a/backend/src/consts.rs b/backend/src/consts.rs index 45167a3..3fff9d6 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -5,7 +5,13 @@ pub const DB_DIRECTORY: &str = "data"; pub const DB_FILENAME: &str = "recipes.sqlite"; pub const SQL_FILENAME: &str = "sql/version_{VERSION}.sql"; pub const VALIDATION_TOKEN_DURATION: i64 = 1 * 60 * 60; // 1 hour. [s]. -pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; pub const COOKIE_AUTH_TOKEN_NAME: &str = "auth_token"; -pub const AUTHENTICATION_TOKEN_SIZE: usize = 32; // Number of alphanumeric characters for cookie authentication token. + +// Number of alphanumeric characters for cookie authentication token. +pub const AUTHENTICATION_TOKEN_SIZE: usize = 32; + pub const SEND_EMAIL_TIMEOUT: Duration = Duration::from_secs(60); + +// HTTP headers, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers. +// Common headers can be found in 'axum::http::header' (which is a re-export of the create 'http'). +pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse proxy (Nginx). diff --git a/backend/src/data/asynchronous.rs b/backend/src/data/asynchronous.rs deleted file mode 100644 index 5d27388..0000000 --- a/backend/src/data/asynchronous.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Functions to be called from actix code. They are asynchonrous and won't block worker thread caller. - -use std::fmt; - -use actix_web::{error::BlockingError, web}; -use chrono::{prelude::*, Duration}; - -use super::db::*; -use crate::model; - -#[derive(Debug)] -pub enum DBAsyncError { - DBError(DBError), - ActixError(BlockingError), - Other(String), -} - -impl fmt::Display for DBAsyncError { - fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for DBAsyncError {} - -impl From for DBAsyncError { - fn from(error: DBError) -> Self { - DBAsyncError::DBError(error) - } -} - -impl From for DBAsyncError { - fn from(error: BlockingError) -> Self { - DBAsyncError::ActixError(error) - } -} - -impl DBAsyncError { - fn from_dyn_error(error: Box) -> Self { - DBAsyncError::Other(error.to_string()) - } -} - -fn combine_errors( - error: std::result::Result, BlockingError>, -) -> Result { - error? -} - -type Result = std::result::Result; - -impl Connection { - pub async fn get_all_recipe_titles_async(&self) -> Result> { - let self_copy = self.clone(); - web::block(move || self_copy.get_all_recipe_titles().unwrap_or_default()) - .await - .map_err(DBAsyncError::from) - } - - pub async fn get_recipe_async(&self, id: i64) -> Result { - let self_copy = self.clone(); - combine_errors( - web::block(move || self_copy.get_recipe(id).map_err(DBAsyncError::from)).await, - ) - } - - pub async fn load_user_async(&self, user_id: i64) -> Result { - let self_copy = self.clone(); - combine_errors( - web::block(move || self_copy.load_user(user_id).map_err(DBAsyncError::from)).await, - ) - } - - pub async fn sign_up_async(&self, email: &str, password: &str) -> Result { - let self_copy = self.clone(); - let email_copy = email.to_string(); - let password_copy = password.to_string(); - combine_errors( - web::block(move || { - self_copy - .sign_up(&email_copy, &password_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } - - pub async fn validation_async( - &self, - token: &str, - validation_time: Duration, - ip: &str, - user_agent: &str, - ) -> Result { - let self_copy = self.clone(); - let token_copy = token.to_string(); - let ip_copy = ip.to_string(); - let user_agent_copy = user_agent.to_string(); - combine_errors( - web::block(move || { - self_copy - .validation(&token_copy, validation_time, &ip_copy, &user_agent_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } - - pub async fn sign_in_async( - &self, - email: &str, - password: &str, - ip: &str, - user_agent: &str, - ) -> Result { - let self_copy = self.clone(); - let email_copy = email.to_string(); - let password_copy = password.to_string(); - let ip_copy = ip.to_string(); - let user_agent_copy = user_agent.to_string(); - combine_errors( - web::block(move || { - self_copy - .sign_in(&email_copy, &password_copy, &ip_copy, &user_agent_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } - - pub async fn authentication_async( - &self, - token: &str, - ip: &str, - user_agent: &str, - ) -> Result { - let self_copy = self.clone(); - let token_copy = token.to_string(); - let ip_copy = ip.to_string(); - let user_agent_copy = user_agent.to_string(); - combine_errors( - web::block(move || { - self_copy - .authentication(&token_copy, &ip_copy, &user_agent_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } - - pub async fn sign_out_async(&self, token: &str) -> Result<()> { - let self_copy = self.clone(); - let token_copy = token.to_string(); - combine_errors( - web::block(move || self_copy.sign_out(&token_copy).map_err(DBAsyncError::from)).await, - ) - } - - pub async fn create_recipe_async(&self, user_id: i64) -> Result { - let self_copy = self.clone(); - combine_errors( - web::block(move || self_copy.create_recipe(user_id).map_err(DBAsyncError::from)).await, - ) - } - - pub async fn set_recipe_title_async(&self, recipe_id: i64, title: &str) -> Result<()> { - let self_copy = self.clone(); - let title_copy = title.to_string(); - combine_errors( - web::block(move || { - self_copy - .set_recipe_title(recipe_id, &title_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } - - pub async fn set_recipe_description_async( - &self, - recipe_id: i64, - description: &str, - ) -> Result<()> { - let self_copy = self.clone(); - let description_copy = description.to_string(); - combine_errors( - web::block(move || { - self_copy - .set_recipe_description(recipe_id, &description_copy) - .map_err(DBAsyncError::from) - }) - .await, - ) - } -} diff --git a/backend/src/data/db.rs b/backend/src/data/db.rs index 5c925e1..c84f72b 100644 --- a/backend/src/data/db.rs +++ b/backend/src/data/db.rs @@ -3,14 +3,16 @@ use std::{ fs::{self, File}, io::Read, path::Path, + str::FromStr, }; use chrono::{prelude::*, Duration}; -use itertools::Itertools; -use r2d2::{Pool, PooledConnection}; -use r2d2_sqlite::SqliteConnectionManager; use rand::distributions::{Alphanumeric, DistString}; -use rusqlite::{named_params, params, OptionalExtension, Params}; +use sqlx::{ + sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}, + Pool, Sqlite, Transaction, +}; +use thiserror::Error; use crate::{ consts, @@ -20,32 +22,19 @@ use crate::{ const CURRENT_DB_VERSION: u32 = 1; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum DBError { - SqliteError(rusqlite::Error), - R2d2Error(r2d2::Error), - UnsupportedVersion(u32), - Other(String), -} + #[error("Sqlx error: {0}")] + Sqlx(#[from] sqlx::Error), -impl fmt::Display for DBError { - fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for DBError {} - -impl From for DBError { - fn from(error: rusqlite::Error) -> Self { - DBError::SqliteError(error) - } -} + #[error( + "Unsupported database version: {0} (code version: {})", + CURRENT_DB_VERSION + )] + UnsupportedVersion(u32), -impl From for DBError { - fn from(error: r2d2::Error) -> Self { - DBError::R2d2Error(error) - } + #[error("Unknown error: {0}")] + Other(String), } impl DBError { @@ -85,92 +74,99 @@ pub enum AuthenticationResult { #[derive(Clone)] pub struct Connection { - pool: Pool, + pool: Pool, } impl Connection { - pub fn new() -> Result { + pub async fn new() -> Result { let path = Path::new(consts::DB_DIRECTORY).join(consts::DB_FILENAME); - Self::new_from_file(path) + Self::new_from_file(path).await } - pub fn new_in_memory() -> Result { - Self::create_connection(SqliteConnectionManager::memory()) + // For tests. + #[warn(dead_code)] + pub async fn new_in_memory() -> Result { + Self::create_connection(SqlitePoolOptions::new().connect("sqlite::memory:").await?).await } - pub fn new_from_file>(file: P) -> Result { + pub async fn new_from_file>(file: P) -> Result { if let Some(data_dir) = file.as_ref().parent() { if !data_dir.exists() { fs::DirBuilder::new().create(data_dir).unwrap(); } } - Self::create_connection(SqliteConnectionManager::file(file)) + let options = SqliteConnectOptions::from_str(&format!( + "sqlite://{}", + file.as_ref().to_str().unwrap() + ))? + .journal_mode(SqliteJournalMode::Wal) // TODO: use 'Wal2' when available. + .create_if_missing(true) + .pragma("foreign_keys", "ON") + .pragma("synchronous", "NORMAL"); + + Self::create_connection(SqlitePoolOptions::new().connect_with(options).await?).await } - fn create_connection(manager: SqliteConnectionManager) -> Result { - let pool = r2d2::Pool::new(manager).unwrap(); + async fn create_connection(pool: Pool) -> Result { let connection = Connection { pool }; - connection.create_or_update_db()?; + connection.create_or_update_db().await?; Ok(connection) } - fn get(&self) -> Result> { - let con = self.pool.get()?; - // ('foreign_keys' is ON by default). - con.pragma_update(None, "synchronous", "NORMAL")?; - Ok(con) + async fn tx(&self) -> Result> { + self.pool.begin().await.map_err(DBError::from) } /// Called after the connection has been established for creating or updating the database. /// The 'Version' table tracks the current state of the database. - fn create_or_update_db(&self) -> Result<()> { - let mut con = self.get()?; - con.pragma_update(None, "journal_mode", "WAL")?; // Note: use "WAL2" when available. - - let tx = con.transaction()?; + async fn create_or_update_db(&self) -> Result<()> { + let mut tx = self.tx().await?; //con.transaction()?; // Check current database version. (Version 0 corresponds to an empty database). - let mut version = { - match tx.query_row( - "SELECT [name] FROM [sqlite_master] WHERE [type] = 'table' AND [name] = 'Version'", - [], - |row| row.get::(0), - ) { - Ok(_) => tx - .query_row( - "SELECT [version] FROM [Version] ORDER BY [id] DESC", - [], - |row| row.get(0), - ) - .unwrap_or_default(), - Err(_) => 0, - } + let mut version = match sqlx::query( + r#" +SELECT [name] FROM [sqlite_master] +WHERE [type] = 'table' AND [name] = 'Version' + "#, + ) + .fetch_one(&mut *tx) + .await + { + Ok(_) => sqlx::query_scalar("SELECT [version] FROM [Version] ORDER BY [id] DESC") + .fetch_optional(&mut *tx) + .await? + .unwrap_or(0), + Err(_) => 0, // If the database doesn't exist. }; - while Self::update_to_next_version(version, &tx)? { + while Self::update_to_next_version(version, &mut tx).await? { version += 1; } - tx.commit()?; + tx.commit().await?; Ok(()) } - fn update_to_next_version(current_version: u32, tx: &rusqlite::Transaction) -> Result { + async fn update_to_next_version( + current_version: u32, + tx: &mut Transaction<'_, Sqlite>, + ) -> Result { let next_version = current_version + 1; if next_version <= CURRENT_DB_VERSION { println!("Update to version {}...", next_version); } - fn update_version(to_version: u32, tx: &rusqlite::Transaction) -> Result<()> { - tx.execute( - "INSERT INTO [Version] ([version], [datetime]) VALUES (?1, datetime('now'))", - [to_version], + async fn update_version(to_version: u32, tx: &mut Transaction<'_, Sqlite>) -> Result<()> { + sqlx::query( + "INSERT INTO [Version] ([version], [datetime]) VALUES ($1, datetime('now'))", ) - .map(|_| ()) - .map_err(DBError::from) + .bind(to_version) + .execute(&mut **tx) + .await?; + Ok(()) } fn ok(updated: bool) -> Result { @@ -183,109 +179,86 @@ impl Connection { match next_version { 1 => { let sql_file = consts::SQL_FILENAME.replace("{VERSION}", &next_version.to_string()); - tx.execute_batch(&load_sql_file(&sql_file)?)?; - update_version(next_version, tx)?; + sqlx::query(&load_sql_file(&sql_file)?) + .execute(&mut **tx) + .await?; + update_version(next_version, tx).await?; ok(true) } - // Version 1 doesn't exist yet. + // Version 2 doesn't exist yet. 2 => ok(false), v => Err(DBError::UnsupportedVersion(v)), } } - pub fn get_all_recipe_titles(&self) -> Result> { - let con = self.get()?; - - let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?; - - let titles: std::result::Result, rusqlite::Error> = stmt - .query_map([], |row| Ok((row.get("id")?, row.get("title")?)))? - .collect(); - - titles.map_err(DBError::from) - } - - /* Not used for the moment. - pub fn get_all_recipes(&self) -> Result> { - let con = self.get()?; - let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?; - let recipes = - stmt.query_map([], |row| { - Ok(model::Recipe::new(row.get(0)?, row.get(1)?)) - })?.map(|r| r.unwrap()).collect_vec(); // TODO: remove unwrap. - Ok(recipes) - } */ - - pub fn get_recipe(&self, id: i64) -> Result { - let con = self.get()?; - con.query_row( - "SELECT [id], [user_id], [title], [description] FROM [Recipe] WHERE [id] = ?1", - [id], - |row| { - Ok(model::Recipe::new( - row.get("id")?, - row.get("user_id")?, - row.get("title")?, - row.get("description")?, - )) - }, + pub async fn get_all_recipe_titles(&self) -> Result> { + sqlx::query_as("SELECT [id], [title] FROM [Recipe] ORDER BY [title]") + .fetch_all(&self.pool) + .await + .map_err(DBError::from) + } + + pub async fn get_recipe(&self, id: i64) -> Result> { + sqlx::query_as( + r#" +SELECT [id], [user_id], [title], [description] +FROM [Recipe] WHERE [id] = $1 + "#, ) + .bind(id) + .fetch_optional(&self.pool) + .await .map_err(DBError::from) } - pub fn get_user_login_info(&self, token: &str) -> Result { - let con = self.get()?; - con.query_row("SELECT [last_login_datetime], [ip], [user_agent] FROM [UserLoginToken] WHERE [token] = ?1", [token], |r| { - Ok(model::UserLoginInfo { - last_login_datetime: r.get("last_login_datetime")?, - ip: r.get("ip")?, - user_agent: r.get("user_agent")?, - }) - }).map_err(DBError::from) - } - - pub fn load_user(&self, user_id: i64) -> Result { - let con = self.get()?; - con.query_row( - "SELECT [email] FROM [User] WHERE [id] = ?1", - [user_id], - |r| { - Ok(model::User { - id: user_id, - email: r.get("email")?, - }) - }, + // For tests. + #[warn(dead_code)] + pub async fn get_user_login_info(&self, token: &str) -> Result { + sqlx::query_as( + r#" +SELECT [last_login_datetime], [ip], [user_agent] +FROM [UserLoginToken] WHERE [token] = $1 + "#, ) + .bind(token) + .fetch_one(&self.pool) + .await .map_err(DBError::from) } - pub fn sign_up(&self, email: &str, password: &str) -> Result { + pub async fn load_user(&self, user_id: i64) -> Result> { + sqlx::query_as("SELECT [id], [email] FROM [User] WHERE [id] = $1") + .bind(user_id) + .fetch_optional(&self.pool) + .await + .map_err(DBError::from) + } + + pub async fn sign_up(&self, email: &str, password: &str) -> Result { self.sign_up_with_given_time(email, password, Utc::now()) + .await } - fn sign_up_with_given_time( + async fn sign_up_with_given_time( &self, email: &str, password: &str, datetime: DateTime, ) -> Result { - let mut con = self.get()?; - let tx = con.transaction()?; - let token = match tx - .query_row( - "SELECT [id], [validation_token] FROM [User] WHERE [email] = ?1", - [email], - |r| { - Ok(( - r.get::<&str, i64>("id")?, - r.get::<&str, Option>("validation_token")?, - )) - }, - ) - .optional()? + let mut tx = self.tx().await?; + + let token = match sqlx::query_as::<_, (i64, Option)>( + r#" +SELECT [id], [validation_token] +FROM [User] WHERE [email] = $1 + "#, + ) + .bind(email) + .fetch_optional(&mut *tx) + .await? { Some((id, validation_token)) => { if validation_token.is_none() { @@ -293,91 +266,95 @@ impl Connection { } let token = generate_token(); let hashed_password = hash(password).map_err(|e| DBError::from_dyn_error(e))?; - tx.execute( - "UPDATE [User] - SET [validation_token] = ?2, [creation_datetime] = ?3, [password] = ?4 - WHERE [id] = ?1", - params![id, token, datetime, hashed_password], - )?; + sqlx::query( + r#" +UPDATE [User] +SET [validation_token] = $2, [creation_datetime] = $3, [password] = $4 +WHERE [id] = $1 + "#, + ) + .bind(id) + .bind(&token) + .bind(datetime) + .bind(hashed_password) + .execute(&mut *tx) + .await?; token } None => { let token = generate_token(); let hashed_password = hash(password).map_err(|e| DBError::from_dyn_error(e))?; - tx.execute( - "INSERT INTO [User] - ([email], [validation_token], [creation_datetime], [password]) - VALUES (?1, ?2, ?3, ?4)", - params![email, token, datetime, hashed_password], - )?; + sqlx::query( + r#" +INSERT INTO [User] +([email], [validation_token], [creation_datetime], [password]) +VALUES ($1, $2, $3, $4) + "#, + ) + .bind(email) + .bind(&token) + .bind(datetime) + .bind(hashed_password) + .execute(&mut *tx) + .await?; token } }; - tx.commit()?; + + tx.commit().await?; + Ok(SignUpResult::UserCreatedWaitingForValidation(token)) } - pub fn validation( + pub async fn validation( &self, token: &str, validation_time: Duration, ip: &str, user_agent: &str, ) -> Result { - let mut con = self.get()?; - let tx = con.transaction()?; - let user_id = match tx - .query_row( - "SELECT [id], [creation_datetime] FROM [User] WHERE [validation_token] = ?1", - [token], - |r| { - Ok(( - r.get::<&str, i64>("id")?, - r.get::<&str, DateTime>("creation_datetime")?, - )) - }, - ) - .optional()? + let mut tx = self.tx().await?; + + let user_id = match sqlx::query_as::<_, (i64, DateTime)>( + "SELECT [id], [creation_datetime] FROM [User] WHERE [validation_token] = $1", + ) + .bind(token) + .fetch_optional(&mut *tx) + .await? { Some((id, creation_datetime)) => { if Utc::now() - creation_datetime > validation_time { return Ok(ValidationResult::ValidationExpired); } - tx.execute( - "UPDATE [User] SET [validation_token] = NULL WHERE [id] = ?1", - [id], - )?; + sqlx::query("UPDATE [User] SET [validation_token] = NULL WHERE [id] = $1") + .bind(id) + .execute(&mut *tx) + .await?; id } None => return Ok(ValidationResult::UnknownUser), }; - let token = Connection::create_login_token(&tx, user_id, ip, user_agent)?; - tx.commit()?; + + let token = Self::create_login_token(&mut tx, user_id, ip, user_agent).await?; + + tx.commit().await?; Ok(ValidationResult::Ok(token, user_id)) } - pub fn sign_in( + pub async fn sign_in( &self, email: &str, password: &str, ip: &str, user_agent: &str, ) -> Result { - let mut con = self.get()?; - let tx = con.transaction()?; - match tx - .query_row( - "SELECT [id], [password], [validation_token] FROM [User] WHERE [email] = ?1", - [email], - |r| { - Ok(( - r.get::<&str, i64>("id")?, - r.get::<&str, String>("password")?, - r.get::<&str, Option>("validation_token")?, - )) - }, - ) - .optional()? + let mut tx = self.tx().await?; + match sqlx::query_as::<_, (i64, String, Option)>( + "SELECT [id], [password], [validation_token] FROM [User] WHERE [email] = $1", + ) + .bind(email) + .fetch_optional(&mut *tx) + .await? { Some((id, stored_password, validation_token)) => { if validation_token.is_some() { @@ -385,8 +362,8 @@ impl Connection { } else if verify_password(password, &stored_password) .map_err(DBError::from_dyn_error)? { - let token = Connection::create_login_token(&tx, id, ip, user_agent)?; - tx.commit()?; + let token = Self::create_login_token(&mut tx, id, ip, user_agent).await?; + tx.commit().await?; Ok(SignInResult::Ok(token, id)) } else { Ok(SignInResult::WrongPassword) @@ -396,138 +373,157 @@ impl Connection { } } - pub fn authentication( + pub async fn authentication( &self, token: &str, ip: &str, user_agent: &str, ) -> Result { - let mut con = self.get()?; - let tx = con.transaction()?; - match tx - .query_row( - "SELECT [id], [user_id] FROM [UserLoginToken] WHERE [token] = ?1", - [token], - |r| Ok((r.get::<&str, i64>("id")?, r.get::<&str, i64>("user_id")?)), - ) - .optional()? + let mut tx = self.tx().await?; + match sqlx::query_as::<_, (i64, i64)>( + "SELECT [id], [user_id] FROM [UserLoginToken] WHERE [token] = $1", + ) + .bind(token) + .fetch_optional(&mut *tx) + .await? { Some((login_id, user_id)) => { - tx.execute( - "UPDATE [UserLoginToken] - SET [last_login_datetime] = ?2, [ip] = ?3, [user_agent] = ?4 - WHERE [id] = ?1", - params![login_id, Utc::now(), ip, user_agent], - )?; - tx.commit()?; + sqlx::query( + r#" +UPDATE [UserLoginToken] +SET [last_login_datetime] = $2, [ip] = $3, [user_agent] = $4 +WHERE [id] = $1 + "#, + ) + .bind(login_id) + .bind(Utc::now()) + .bind(ip) + .bind(user_agent) + .execute(&mut *tx) + .await?; + tx.commit().await?; Ok(AuthenticationResult::Ok(user_id)) } None => Ok(AuthenticationResult::NotValidToken), } } - pub fn sign_out(&self, token: &str) -> Result<()> { - let mut con = self.get()?; - let tx = con.transaction()?; - match tx - .query_row( - "SELECT [id] FROM [UserLoginToken] WHERE [token] = ?1", - [token], - |r| Ok(r.get::<&str, i64>("id")?), - ) - .optional()? + pub async fn sign_out(&self, token: &str) -> Result<()> { + let mut tx = self.tx().await?; + match sqlx::query_scalar::<_, i64>("SELECT [id] FROM [UserLoginToken] WHERE [token] = $1") + .bind(token) + .fetch_optional(&mut *tx) + .await? { Some(login_id) => { - tx.execute( - "DELETE FROM [UserLoginToken] WHERE [id] = ?1", - params![login_id], - )?; - tx.commit()? + sqlx::query("DELETE FROM [UserLoginToken] WHERE [id] = $1") + .bind(login_id) + .execute(&mut *tx) + .await?; + tx.commit().await?; } None => (), } Ok(()) } - pub fn create_recipe(&self, user_id: i64) -> Result { - let con = self.get()?; - - // Verify if an empty recipe already exists. Returns its id if one exists. - match con - .query_row( - "SELECT [Recipe].[id] FROM [Recipe] - LEFT JOIN [Image] ON [Image].[recipe_id] = [Recipe].[id] - LEFT JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] - WHERE [Recipe].[user_id] = ?1 - AND [Recipe].[title] = '' - AND [Recipe].[estimate_time] IS NULL - AND [Recipe].[description] = '' - AND [Image].[id] IS NULL - AND [Group].[id] IS NULL", - [user_id], - |r| Ok(r.get::<&str, i64>("id")?), - ) - .optional()? + pub async fn create_recipe(&self, user_id: i64) -> Result { + let mut tx = self.tx().await?; + + match sqlx::query_scalar::<_, i64>( + r#" +SELECT [Recipe].[id] FROM [Recipe] +LEFT JOIN [Image] ON [Image].[recipe_id] = [Recipe].[id] +LEFT JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] +WHERE [Recipe].[user_id] = $1 + AND [Recipe].[title] = '' + AND [Recipe].[estimate_time] IS NULL + AND [Recipe].[description] = '' + AND [Image].[id] IS NULL + AND [Group].[id] IS NULL + "#, + ) + .bind(user_id) + .fetch_optional(&mut *tx) + .await? { Some(recipe_id) => Ok(recipe_id), None => { - con.execute( - "INSERT INTO [Recipe] ([user_id], [title]) VALUES (?1, '')", - [user_id], - )?; - Ok(con.last_insert_rowid()) + let db_result = + sqlx::query("INSERT INTO [Recipe] ([user_id], [title]) VALUES ($1, '')") + .bind(user_id) + .execute(&mut *tx) + .await?; + + tx.commit().await?; + Ok(db_result.last_insert_rowid()) } } } - pub fn set_recipe_title(&self, recipe_id: i64, title: &str) -> Result<()> { - let con = self.get()?; - con.execute( - "UPDATE [Recipe] SET [title] = ?2 WHERE [id] = ?1", - params![recipe_id, title], - ) - .map(|_n| ()) - .map_err(DBError::from) + pub async fn set_recipe_title(&self, recipe_id: i64, title: &str) -> Result<()> { + sqlx::query("UPDATE [Recipe] SET [title] = $2 WHERE [id] = $1") + .bind(recipe_id) + .bind(title) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) } - pub fn set_recipe_description(&self, recipe_id: i64, description: &str) -> Result<()> { - let con = self.get()?; - con.execute( - "UPDATE [Recipe] SET [description] = ?2 WHERE [id] = ?1", - params![recipe_id, description], - ) - .map(|_n| ()) - .map_err(DBError::from) + pub async fn set_recipe_description(&self, recipe_id: i64, description: &str) -> Result<()> { + sqlx::query("UPDATE [Recipe] SET [description] = $2 WHERE [id] = $1") + .bind(recipe_id) + .bind(description) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) } /// Execute a given SQL file. - pub fn execute_file + fmt::Display>(&self, file: P) -> Result<()> { - let con = self.get()?; + pub async fn execute_file + fmt::Display>(&self, file: P) -> Result<()> { let sql = load_sql_file(file)?; - con.execute_batch(&sql).map_err(DBError::from) + sqlx::query(&sql) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) } - /// Execute any SQL statement. - /// Mainly used for testing. - pub fn execute_sql(&self, sql: &str, params: P) -> Result { - let con = self.get()?; - con.execute(sql, params).map_err(DBError::from) + pub async fn execute_sql<'a>( + &self, + query: sqlx::query::Query<'a, Sqlite, sqlx::sqlite::SqliteArguments<'a>>, + ) -> Result { + query + .execute(&self.pool) + .await + .map(|db_result| db_result.rows_affected()) + .map_err(DBError::from) } // Return the token. - fn create_login_token( - tx: &rusqlite::Transaction, + async fn create_login_token( + tx: &mut sqlx::Transaction<'_, Sqlite>, user_id: i64, ip: &str, user_agent: &str, ) -> Result { let token = generate_token(); - tx.execute( - "INSERT INTO [UserLoginToken] - ([user_id], [last_login_datetime], [token], [ip], [user_agent]) - VALUES (?1, ?2, ?3, ?4, ?5)", - params![user_id, Utc::now(), token, ip, user_agent], - )?; + sqlx::query( + r#" +INSERT INTO [UserLoginToken] +([user_id], [last_login_datetime], [token], [ip], [user_agent]) +VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(user_id) + .bind(Utc::now()) + .bind(&token) + .bind(ip) + .bind(user_agent) + .execute(&mut **tx) + .await?; Ok(token) } } @@ -558,52 +554,57 @@ fn generate_token() -> String { #[cfg(test)] mod tests { use super::*; - use rusqlite::{ffi, types::Value, Error, ErrorCode}; - #[test] - fn sign_up() -> Result<()> { - let connection = Connection::new_in_memory()?; - match connection.sign_up("paul@atreides.com", "12345")? { + #[tokio::test] + async fn sign_up() -> Result<()> { + let connection = Connection::new_in_memory().await?; + match connection.sign_up("paul@atreides.com", "12345").await? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_to_an_already_existing_user() -> Result<()> { - let connection = Connection::new_in_memory()?; - connection.execute_sql(" - INSERT INTO - [User] ([id], [email], [name], [password], [creation_datetime], [validation_token]) - VALUES ( - 1, - 'paul@atreides.com', - 'paul', - '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', - 0, - NULL - );", [])?; - match connection.sign_up("paul@atreides.com", "12345")? { + #[tokio::test] + async fn sign_up_to_an_already_existing_user() -> Result<()> { + let connection = Connection::new_in_memory().await?; + connection.execute_sql( + sqlx::query( + r#" +INSERT INTO + [User] ([id], [email], [name], [password], [creation_datetime], [validation_token]) + VALUES ( + 1, + 'paul@atreides.com', + 'paul', + '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', + 0, + NULL + ); + "#)).await?; + match connection.sign_up("paul@atreides.com", "12345").await? { SignUpResult::UserAlreadyExists => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_and_sign_in_without_validation() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn sign_up_and_sign_in_without_validation() -> Result<()> { + let connection = Connection::new_in_memory().await?; let email = "paul@atreides.com"; let password = "12345"; - match connection.sign_up(email, password)? { + match connection.sign_up(email, password).await? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } - match connection.sign_in(email, password, "127.0.0.1", "Mozilla/5.0")? { + match connection + .sign_in(email, password, "127.0.0.1", "Mozilla/5.0") + .await? + { SignInResult::AccountNotValidated => (), // Nominal case. other => panic!("{:?}", other), } @@ -611,116 +612,134 @@ mod tests { Ok(()) } - #[test] - fn sign_up_to_an_unvalidated_already_existing_user() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn sign_up_to_an_unvalidated_already_existing_user() -> Result<()> { + let connection = Connection::new_in_memory().await?; let token = generate_token(); - connection.execute_sql(" - INSERT INTO - [User] ([id], [email], [name], [password], [creation_datetime], [validation_token]) - VALUES ( - 1, - 'paul@atreides.com', - 'paul', - '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', - 0, - :token - );", named_params! { ":token": token })?; - match connection.sign_up("paul@atreides.com", "12345")? { + connection.execute_sql( + sqlx::query( + r#" +INSERT INTO [User] + ([id], [email], [name], [password], [creation_datetime], [validation_token]) +VALUES ( + 1, + 'paul@atreides.com', + 'paul', + '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', + 0, + $1 +) + "# + ).bind(token)).await?; + match connection.sign_up("paul@atreides.com", "12345").await? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_then_send_validation_at_time() -> Result<()> { - let connection = Connection::new_in_memory()?; - let validation_token = match connection.sign_up("paul@atreides.com", "12345")? { + #[tokio::test] + async fn sign_up_then_send_validation_at_time() -> Result<()> { + let connection = Connection::new_in_memory().await?; + let validation_token = match connection.sign_up("paul@atreides.com", "12345").await? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; - match connection.validation( - &validation_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla/5.0", - )? { + match connection + .validation( + &validation_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla/5.0", + ) + .await? + { ValidationResult::Ok(_, _) => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_then_send_validation_too_late() -> Result<()> { - let connection = Connection::new_in_memory()?; - let validation_token = match connection.sign_up_with_given_time( - "paul@atreides.com", - "12345", - Utc::now() - Duration::days(1), - )? { + #[tokio::test] + async fn sign_up_then_send_validation_too_late() -> Result<()> { + let connection = Connection::new_in_memory().await?; + let validation_token = match connection + .sign_up_with_given_time("paul@atreides.com", "12345", Utc::now() - Duration::days(1)) + .await? + { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; - match connection.validation( - &validation_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla/5.0", - )? { + match connection + .validation( + &validation_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla/5.0", + ) + .await? + { ValidationResult::ValidationExpired => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_then_send_validation_with_bad_token() -> Result<()> { - let connection = Connection::new_in_memory()?; - let _validation_token = match connection.sign_up("paul@atreides.com", "12345")? { + #[tokio::test] + async fn sign_up_then_send_validation_with_bad_token() -> Result<()> { + let connection = Connection::new_in_memory().await?; + let _validation_token = match connection.sign_up("paul@atreides.com", "12345").await? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; let random_token = generate_token(); - match connection.validation( - &random_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla/5.0", - )? { + match connection + .validation( + &random_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla/5.0", + ) + .await? + { ValidationResult::UnknownUser => (), // Nominal case. other => panic!("{:?}", other), } Ok(()) } - #[test] - fn sign_up_then_send_validation_then_sign_in() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn sign_up_then_send_validation_then_sign_in() -> Result<()> { + let connection = Connection::new_in_memory().await?; let email = "paul@atreides.com"; let password = "12345"; // Sign up. - let validation_token = match connection.sign_up(email, password)? { + let validation_token = match connection.sign_up(email, password).await? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; // Validation. - match connection.validation( - &validation_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla/5.0", - )? { + match connection + .validation( + &validation_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla/5.0", + ) + .await? + { ValidationResult::Ok(_, _) => (), other => panic!("{:?}", other), }; // Sign in. - match connection.sign_in(email, password, "127.0.0.1", "Mozilla/5.0")? { + match connection + .sign_in(email, password, "127.0.0.1", "Mozilla/5.0") + .await? + { SignInResult::Ok(_, _) => (), // Nominal case. other => panic!("{:?}", other), } @@ -728,94 +747,113 @@ mod tests { Ok(()) } - #[test] - fn sign_up_then_send_validation_then_authentication() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn sign_up_then_send_validation_then_authentication() -> Result<()> { + let connection = Connection::new_in_memory().await?; let email = "paul@atreides.com"; let password = "12345"; // Sign up. - let validation_token = match connection.sign_up(email, password)? { + let validation_token = match connection.sign_up(email, password).await? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; // Validation. - let (authentication_token, user_id) = match connection.validation( - &validation_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla", - )? { + let (authentication_token, user_id) = match connection + .validation( + &validation_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla", + ) + .await? + { ValidationResult::Ok(token, user_id) => (token, user_id), other => panic!("{:?}", other), }; // Check user login information. - let user_login_info_1 = connection.get_user_login_info(&authentication_token)?; + let user_login_info_1 = connection + .get_user_login_info(&authentication_token) + .await?; assert_eq!(user_login_info_1.ip, "127.0.0.1"); assert_eq!(user_login_info_1.user_agent, "Mozilla"); // Authentication. - let _user_id = - match connection.authentication(&authentication_token, "192.168.1.1", "Chrome")? { - AuthenticationResult::Ok(user_id) => user_id, // Nominal case. - other => panic!("{:?}", other), - }; + let _user_id = match connection + .authentication(&authentication_token, "192.168.1.1", "Chrome") + .await? + { + AuthenticationResult::Ok(user_id) => user_id, // Nominal case. + other => panic!("{:?}", other), + }; // Check user login information. - let user_login_info_2 = connection.get_user_login_info(&authentication_token)?; + let user_login_info_2 = connection + .get_user_login_info(&authentication_token) + .await?; + assert_eq!(user_login_info_2.ip, "192.168.1.1"); assert_eq!(user_login_info_2.user_agent, "Chrome"); Ok(()) } - #[test] - fn sign_up_then_send_validation_then_sign_out_then_sign_in() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn sign_up_then_send_validation_then_sign_out_then_sign_in() -> Result<()> { + let connection = Connection::new_in_memory().await?; let email = "paul@atreides.com"; let password = "12345"; // Sign up. - let validation_token = match connection.sign_up(email, password)? { + let validation_token = match connection.sign_up(email, password).await? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; // Validation. - let (authentication_token_1, user_id_1) = match connection.validation( - &validation_token, - Duration::hours(1), - "127.0.0.1", - "Mozilla", - )? { + let (authentication_token_1, user_id_1) = match connection + .validation( + &validation_token, + Duration::hours(1), + "127.0.0.1", + "Mozilla", + ) + .await? + { ValidationResult::Ok(token, user_id) => (token, user_id), other => panic!("{:?}", other), }; // Check user login information. - let user_login_info_1 = connection.get_user_login_info(&authentication_token_1)?; + let user_login_info_1 = connection + .get_user_login_info(&authentication_token_1) + .await?; assert_eq!(user_login_info_1.ip, "127.0.0.1"); assert_eq!(user_login_info_1.user_agent, "Mozilla"); // Sign out. - connection.sign_out(&authentication_token_1)?; + connection.sign_out(&authentication_token_1).await?; // Sign in. - let (authentication_token_2, user_id_2) = - match connection.sign_in(email, password, "192.168.1.1", "Chrome")? { - SignInResult::Ok(token, user_id) => (token, user_id), - other => panic!("{:?}", other), - }; + let (authentication_token_2, user_id_2) = match connection + .sign_in(email, password, "192.168.1.1", "Chrome") + .await? + { + SignInResult::Ok(token, user_id) => (token, user_id), + other => panic!("{:?}", other), + }; assert_eq!(user_id_1, user_id_2); assert_ne!(authentication_token_1, authentication_token_2); // Check user login information. - let user_login_info_2 = connection.get_user_login_info(&authentication_token_2)?; + let user_login_info_2 = connection + .get_user_login_info(&authentication_token_2) + .await?; assert_eq!(user_login_info_2.ip, "192.168.1.1"); assert_eq!(user_login_info_2.user_agent, "Chrome"); @@ -823,44 +861,44 @@ mod tests { Ok(()) } - #[test] - fn create_a_new_recipe_then_update_its_title() -> Result<()> { - let connection = Connection::new_in_memory()?; + #[tokio::test] + async fn create_a_new_recipe_then_update_its_title() -> Result<()> { + let connection = Connection::new_in_memory().await?; connection.execute_sql( - "INSERT INTO [User] - ([id], [email], [name], [password], [creation_datetime], [validation_token]) - VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - params![ - 1, - "paul@atreides.com", - "paul", - "$argon2id$v=19$m=4096,t=3,p=1$G4fjepS05MkRbTqEImUdYg$GGziE8uVQe1L1oFHk37lBno10g4VISnVqynSkLCH3Lc", - "2022-11-29 22:05:04.121407300+00:00", - Value::Null, - ] - )?; - - match connection.create_recipe(2) { - Err(DBError::SqliteError(Error::SqliteFailure( - ffi::Error { - code: ErrorCode::ConstraintViolation, - extended_code: _, - }, - Some(_), - ))) => (), // Nominal case. + sqlx::query( + r#" +INSERT INTO [User] + ([id], [email], [name], [password], [creation_datetime], [validation_token]) +VALUES + ($1, $2, $3, $4, $5, $6) + "# + ) + .bind(1) + .bind("paul@atreides.com") + .bind("paul") + .bind("$argon2id$v=19$m=4096,t=3,p=1$G4fjepS05MkRbTqEImUdYg$GGziE8uVQe1L1oFHk37lBno10g4VISnVqynSkLCH3Lc") + .bind("2022-11-29 22:05:04.121407300+00:00") + .bind(None::<&str>) // 'null'. + ).await?; + + match connection.create_recipe(2).await { + Err(DBError::Sqlx(sqlx::Error::Database(err))) => { + // SQLITE_CONSTRAINT_FOREIGNKEY + // https://www.sqlite.org/rescode.html#constraint_foreignkey + assert_eq!(err.code(), Some(std::borrow::Cow::from("787"))); + } // Nominal case. TODO: check 'err' value. other => panic!( "Creating a recipe with an inexistant user must fail: {:?}", other ), } - let recipe_id = connection.create_recipe(1)?; + let recipe_id = connection.create_recipe(1).await?; assert_eq!(recipe_id, 1); - connection.set_recipe_title(recipe_id, "Crêpe")?; - - let recipe = connection.get_recipe(recipe_id)?; + connection.set_recipe_title(recipe_id, "Crêpe").await?; + let recipe = connection.get_recipe(recipe_id).await?.unwrap(); assert_eq!(recipe.title, "Crêpe".to_string()); Ok(()) diff --git a/backend/src/data/mod.rs b/backend/src/data/mod.rs index edc9cb9..98b6ecd 100644 --- a/backend/src/data/mod.rs +++ b/backend/src/data/mod.rs @@ -1,2 +1,2 @@ -pub mod asynchronous; pub mod db; +mod utils; diff --git a/backend/src/data/utils.rs b/backend/src/data/utils.rs new file mode 100644 index 0000000..c32cfa2 --- /dev/null +++ b/backend/src/data/utils.rs @@ -0,0 +1,33 @@ +use sqlx::{sqlite::SqliteRow, FromRow, Row}; + +use crate::model; + +impl FromRow<'_, SqliteRow> for model::Recipe { + fn from_row(row: &SqliteRow) -> sqlx::Result { + Ok(model::Recipe::new( + row.try_get("id")?, + row.try_get("user_id")?, + row.try_get("title")?, + row.try_get("description")?, + )) + } +} + +impl FromRow<'_, SqliteRow> for model::UserLoginInfo { + fn from_row(row: &SqliteRow) -> sqlx::Result { + Ok(model::UserLoginInfo { + last_login_datetime: row.try_get("last_login_datetime")?, + ip: row.try_get("ip")?, + user_agent: row.try_get("user_agent")?, + }) + } +} + +impl FromRow<'_, SqliteRow> for model::User { + fn from_row(row: &SqliteRow) -> sqlx::Result { + Ok(model::User { + id: row.try_get("id")?, + email: row.try_get("email")?, + }) + } +} diff --git a/backend/src/email.rs b/backend/src/email.rs index 4d906ff..a167e86 100644 --- a/backend/src/email.rs +++ b/backend/src/email.rs @@ -1,6 +1,8 @@ use derive_more::Display; -use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport}; -use std::time::Duration; +use lettre::{ + transport::smtp::{authentication::Credentials, AsyncSmtpTransport}, + AsyncTransport, Message, Tokio1Executor, +}; use crate::consts; @@ -29,10 +31,11 @@ impl From for Error { } } -pub fn send_validation( +pub async fn send_validation( site_url: &str, email: &str, token: &str, + smtp_relay_address: &str, smtp_login: &str, smtp_password: &str, ) -> Result<(), Error> { @@ -40,20 +43,20 @@ pub fn send_validation( .message_id(None) .from("recipes@gburri.org".parse()?) .to(email.parse()?) - .subject("Recipes.gburri.org account validation") + .subject("recipes.gburri.org account validation") .body(format!( - "Follow this link to confirm your inscription: {}/validation?token={}", + "Follow this link to confirm your inscription: {}/validation?validation_token={}", site_url, token ))?; let credentials = Credentials::new(smtp_login.to_string(), smtp_password.to_string()); - let mailer = SmtpTransport::relay("mail.gandi.net")? + let mailer = AsyncSmtpTransport::::relay(smtp_relay_address)? .credentials(credentials) .timeout(Some(consts::SEND_EMAIL_TIMEOUT)) .build(); - if let Err(error) = mailer.send(&email) { + if let Err(error) = mailer.send(email).await { eprintln!("Error when sending E-mail:\n{:?}", &error); } diff --git a/backend/src/main.rs b/backend/src/main.rs index 97eaa17..65a778b 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,9 +1,17 @@ -use std::path::Path; - -use actix_files as fs; -use actix_web::{middleware, web, App, HttpServer}; +use std::{net::SocketAddr, path::Path}; + +use axum::{ + extract::{ConnectInfo, FromRef, Request, State}, + middleware::{self, Next}, + response::{Response, Result}, + routing::get, + Router, +}; +use axum_extra::extract::cookie::CookieJar; use chrono::prelude::*; use clap::Parser; +use config::Config; +use tower_http::services::ServeDir; use data::db; @@ -16,49 +24,121 @@ mod model; mod services; mod utils; -#[actix_web::main] -async fn main() -> std::io::Result<()> { - if process_args() { - return Ok(()); +#[derive(Clone)] +struct AppState { + config: Config, + db_connection: db::Connection, +} + +impl FromRef for Config { + fn from_ref(app_state: &AppState) -> Config { + app_state.config.clone() + } +} + +impl FromRef for db::Connection { + fn from_ref(app_state: &AppState) -> db::Connection { + app_state.db_connection.clone() } +} - std::env::set_var("RUST_LOG", "info,actix_web=info"); - env_logger::init(); +// TODO: Should main returns 'Result'? +#[tokio::main] +async fn main() { + if process_args().await { + return; + } println!("Starting Recipes as web server..."); - let config = web::Data::new(config::load()); - let port = config.as_ref().port; + let config = config::load(); + let port = config.port; println!("Configuration: {:?}", config); - let db_connection = web::Data::new(db::Connection::new().unwrap()); - - let server = HttpServer::new(move || { - App::new() - .wrap(middleware::Logger::default()) - .wrap(middleware::Compress::default()) - .app_data(db_connection.clone()) - .app_data(config.clone()) - .service(services::home_page) - .service(services::sign_up_get) - .service(services::sign_up_post) - .service(services::sign_up_check_email) - .service(services::sign_up_validation) - .service(services::sign_in_get) - .service(services::sign_in_post) - .service(services::sign_out) - .service(services::view_recipe) - .service(services::edit_recipe) - .service(services::new_recipe) - .service(services::webapi::set_recipe_title) - .service(services::webapi::set_recipe_description) - .service(fs::Files::new("/static", "static")) - .default_service(web::to(services::not_found)) - }); - //.workers(1); - - server.bind(&format!("0.0.0.0:{}", port))?.run().await + tracing_subscriber::fmt::init(); + + let db_connection = db::Connection::new().await.unwrap(); + + let state = AppState { + config, + db_connection, + }; + + let app = Router::new() + .route("/", get(services::home_page)) + .route( + "/signup", + get(services::sign_up_get).post(services::sign_up_post), + ) + .route("/validation", get(services::sign_up_validation)) + .route("/recipe/view/:id", get(services::view_recipe)) + .route( + "/signin", + get(services::sign_in_get).post(services::sign_in_post), + ) + .route("/signout", get(services::sign_out)) + .route_layer(middleware::from_fn_with_state( + state.clone(), + user_authentication, + )) + .nest_service("/static", ServeDir::new("static")) + .fallback(services::not_found) + .with_state(state) + .into_make_service_with_connect_info::(); + + let addr = SocketAddr::from(([0, 0, 0, 0], port)); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + + axum::serve(listener, app).await.unwrap(); +} + +async fn user_authentication( + ConnectInfo(addr): ConnectInfo, + State(connection): State, + mut req: Request, + next: Next, +) -> Result { + let jar = CookieJar::from_headers(req.headers()); + let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(req.headers(), addr); + let user = get_current_user(connection, &jar, &client_ip, &client_user_agent).await; + req.extensions_mut().insert(user); + Ok(next.run(req).await) +} + +async fn get_current_user( + connection: db::Connection, + jar: &CookieJar, + client_ip: &str, + client_user_agent: &str, +) -> Option { + match jar.get(consts::COOKIE_AUTH_TOKEN_NAME) { + Some(token_cookie) => match connection + .authentication(token_cookie.value(), &client_ip, &client_user_agent) + .await + { + Ok(db::AuthenticationResult::NotValidToken) => { + // TODO: remove cookie? + None + } + Ok(db::AuthenticationResult::Ok(user_id)) => { + match connection.load_user(user_id).await { + Ok(user) => user, + Err(error) => { + // TODO: Return 'Result'? + println!("Error during authentication: {}", error); + None + } + } + } + Err(error) => { + // TODO: Return 'Result'? + println!("Error during authentication: {}", error); + None + } + }, + None => None, + } } #[derive(Parser, Debug)] @@ -68,7 +148,7 @@ struct Args { dbtest: bool, } -fn process_args() -> bool { +async fn process_args() -> bool { let args = Args::parse(); if args.dbtest { @@ -93,16 +173,18 @@ fn process_args() -> bool { .expect(&format!("Unable to remove db file: {:?}", &db_path)); } - match db::Connection::new() { + match db::Connection::new().await { Ok(con) => { - if let Err(error) = con.execute_file("sql/data_test.sql") { + if let Err(error) = con.execute_file("sql/data_test.sql").await { eprintln!("{}", error); } // Set the creation datetime to 'now'. con.execute_sql( - "UPDATE [User] SET [creation_datetime] = ?1 WHERE [email] = 'paul@test.org'", - [Utc::now()], + sqlx::query( + "UPDATE [User] SET [creation_datetime] = ?1 WHERE [email] = 'paul@test.org'") + .bind(Utc::now()) ) + .await .unwrap(); } Err(error) => { diff --git a/backend/src/model.rs b/backend/src/model.rs index 0ed4825..011cab2 100644 --- a/backend/src/model.rs +++ b/backend/src/model.rs @@ -1,5 +1,6 @@ use chrono::prelude::*; +#[derive(Debug, Clone)] pub struct User { pub id: i64, pub email: String, diff --git a/backend/src/services.rs b/backend/src/services.rs index 63a7461..a8f440d 100644 --- a/backend/src/services.rs +++ b/backend/src/services.rs @@ -1,145 +1,29 @@ -use std::collections::HashMap; - -use actix_web::{ - cookie::Cookie, - get, - http::{header, header::ContentType, StatusCode}, - post, web, HttpRequest, HttpResponse, Responder, +use std::{collections::HashMap, net::SocketAddr}; + +use askama::Template; +use axum::{ + body::Body, + debug_handler, + extract::{ConnectInfo, Extension, Host, Path, Query, Request, State}, + http::{HeaderMap, StatusCode}, + response::{IntoResponse, Redirect, Response, Result}, + Form, }; -use askama_actix::{Template, TemplateToResponse}; +use axum_extra::extract::cookie::{Cookie, CookieJar}; use chrono::Duration; -use log::{debug, error, info, log_enabled, Level}; use serde::Deserialize; -use crate::{ - config::Config, - consts, - data::{asynchronous, db}, - email, model, utils, -}; +use crate::{config::Config, consts, data::db, email, model, utils, AppState}; pub mod webapi; -///// UTILS ///// - -fn get_ip_and_user_agent(req: &HttpRequest) -> (String, String) { - let ip = match req.headers().get(consts::REVERSE_PROXY_IP_HTTP_FIELD) { - Some(v) => v.to_str().unwrap_or_default().to_string(), - None => req - .peer_addr() - .map(|addr| addr.ip().to_string()) - .unwrap_or_default(), - }; - - let user_agent = req - .headers() - .get(header::USER_AGENT) - .map(|v| v.to_str().unwrap_or_default()) - .unwrap_or_default() - .to_string(); - - (ip, user_agent) -} - -async fn get_current_user( - req: &HttpRequest, - connection: web::Data, -) -> Option { - let (client_ip, client_user_agent) = get_ip_and_user_agent(req); - - match req.cookie(consts::COOKIE_AUTH_TOKEN_NAME) { - Some(token_cookie) => match connection - .authentication_async(token_cookie.value(), &client_ip, &client_user_agent) - .await - { - Ok(db::AuthenticationResult::NotValidToken) => - // TODO: remove cookie? - { - None - } - Ok(db::AuthenticationResult::Ok(user_id)) => { - match connection.load_user_async(user_id).await { - Ok(user) => Some(user), - Err(error) => { - error!("Error during authentication: {}", error); - None - } - } - } - Err(error) => { - error!("Error during authentication: {}", error); - None - } - }, - None => None, - } -} - -type Result = std::result::Result; - -///// ERROR ///// - -#[derive(Debug)] -pub struct ServiceError { - status_code: StatusCode, - message: Option, -} - -impl From for ServiceError { - fn from(error: asynchronous::DBAsyncError) -> Self { - ServiceError { - status_code: StatusCode::INTERNAL_SERVER_ERROR, - message: Some(format!("{:?}", error)), - } - } -} - -impl From for ServiceError { - fn from(error: email::Error) -> Self { - ServiceError { - status_code: StatusCode::INTERNAL_SERVER_ERROR, - message: Some(format!("{:?}", error)), - } - } -} - -impl From for ServiceError { - fn from(error: actix_web::error::BlockingError) -> Self { - ServiceError { - status_code: StatusCode::INTERNAL_SERVER_ERROR, - message: Some(format!("{:?}", error)), - } - } -} - -impl From for ServiceError { - fn from(error: ron::error::SpannedError) -> Self { - ServiceError { - status_code: StatusCode::INTERNAL_SERVER_ERROR, - message: Some(format!("{:?}", error)), - } - } -} - -impl std::fmt::Display for ServiceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - if let Some(ref m) = self.message { - write!(f, "**{}**\n\n", m)?; - } - write!(f, "Code: {}", self.status_code) - } -} - -impl actix_web::error::ResponseError for ServiceError { - fn error_response(&self) -> HttpResponse { - MessageBaseTemplate { +impl axum::response::IntoResponse for db::DBError { + fn into_response(self) -> Response { + let body = MessageTemplate { + user: None, message: &self.to_string(), - } - .to_response() - } - - fn status_code(&self) -> StatusCode { - self.status_code + }; + (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() } } @@ -153,20 +37,18 @@ struct HomeTemplate { current_recipe_id: Option, } -#[get("/")] +#[debug_handler] pub async fn home_page( - req: HttpRequest, - connection: web::Data, -) -> Result { - let user = get_current_user(&req, connection.clone()).await; - let recipes = connection.get_all_recipe_titles_async().await?; + State(connection): State, + Extension(user): Extension>, +) -> Result { + let recipes = connection.get_all_recipe_titles().await?; Ok(HomeTemplate { user, current_recipe_id: None, recipes, - } - .to_response()) + }) } ///// VIEW RECIPE ///// @@ -177,115 +59,117 @@ struct ViewRecipeTemplate { user: Option, recipes: Vec<(i64, String)>, current_recipe_id: Option, - current_recipe: model::Recipe, } -#[get("/recipe/view/{id}")] +#[debug_handler] pub async fn view_recipe( - req: HttpRequest, - path: web::Path<(i64,)>, - connection: web::Data, -) -> Result { - let (id,) = path.into_inner(); - let user = get_current_user(&req, connection.clone()).await; - let recipes = connection.get_all_recipe_titles_async().await?; - let recipe = connection.get_recipe_async(id).await?; - - Ok(ViewRecipeTemplate { - user, - current_recipe_id: Some(recipe.id), - recipes, - current_recipe: recipe, - } - .to_response()) -} - -///// EDIT/NEW RECIPE ///// - -#[derive(Template)] -#[template(path = "edit_recipe.html")] -struct EditRecipeTemplate { - user: Option, - recipes: Vec<(i64, String)>, - current_recipe_id: Option, - - current_recipe: model::Recipe, -} - -#[get("/recipe/edit/{id}")] -pub async fn edit_recipe( - req: HttpRequest, - path: web::Path<(i64,)>, - connection: web::Data, -) -> Result { - let (id,) = path.into_inner(); - let user = match get_current_user(&req, connection.clone()).await { - Some(u) => u, - None => { - return Ok(MessageTemplate { - user: None, - message: "Cannot edit a recipe without being logged in", - } - .to_response()) + State(connection): State, + Extension(user): Extension>, + Path(recipe_id): Path, +) -> Result { + let recipes = connection.get_all_recipe_titles().await?; + match connection.get_recipe(recipe_id).await? { + Some(recipe) => Ok(ViewRecipeTemplate { + user, + current_recipe_id: Some(recipe.id), + recipes, + current_recipe: recipe, } - }; - - let recipe = connection.get_recipe_async(id).await?; - - if recipe.user_id != user.id { - return Ok(MessageTemplate { - message: "Cannot edit a recipe you don't own", - user: Some(user), + .into_response()), + None => Ok(MessageTemplate { + user, + message: &format!("Cannot find the recipe {}", recipe_id), } - .to_response()); - } - - let recipes = connection.get_all_recipe_titles_async().await?; - - Ok(EditRecipeTemplate { - user: Some(user), - current_recipe_id: Some(recipe.id), - recipes, - current_recipe: recipe, + .into_response()), } - .to_response()) } -#[get("/recipe/new")] -pub async fn new_recipe( - req: HttpRequest, - connection: web::Data, -) -> Result { - let user = match get_current_user(&req, connection.clone()).await { - Some(u) => u, - None => { - return Ok(MessageTemplate { - message: "Cannot create a recipe without being logged in", - user: None, - } - .to_response()) - } - }; +///// EDIT/NEW RECIPE ///// - let recipe_id = connection.create_recipe_async(user.id).await?; - let recipes = connection.get_all_recipe_titles_async().await?; - let user_id = user.id; - - Ok(EditRecipeTemplate { - user: Some(user), - current_recipe_id: Some(recipe_id), - recipes, - current_recipe: model::Recipe::empty(recipe_id, user_id), - } - .to_response()) -} +// #[derive(Template)] +// #[template(path = "edit_recipe.html")] +// struct EditRecipeTemplate { +// user: Option, +// recipes: Vec<(i64, String)>, +// current_recipe_id: Option, + +// current_recipe: model::Recipe, +// } + +// #[get("/recipe/edit/{id}")] +// pub async fn edit_recipe( +// req: HttpRequest, +// path: web::Path<(i64,)>, +// connection: web::Data, +// ) -> Result { +// let (id,) = path.into_inner(); +// let user = match get_current_user(&req, connection.clone()).await { +// Some(u) => u, +// None => { +// return Ok(MessageTemplate { +// user: None, +// message: "Cannot edit a recipe without being logged in", +// } +// .to_response()) +// } +// }; + +// let recipe = connection.get_recipe_async(id).await?; + +// if recipe.user_id != user.id { +// return Ok(MessageTemplate { +// message: "Cannot edit a recipe you don't own", +// user: Some(user), +// } +// .to_response()); +// } + +// let recipes = connection.get_all_recipe_titles_async().await?; + +// Ok(EditRecipeTemplate { +// user: Some(user), +// current_recipe_id: Some(recipe.id), +// recipes, +// current_recipe: recipe, +// } +// .to_response()) +// } + +// #[get("/recipe/new")] +// pub async fn new_recipe( +// req: HttpRequest, +// connection: web::Data, +// ) -> Result { +// let user = match get_current_user(&req, connection.clone()).await { +// Some(u) => u, +// None => { +// return Ok(MessageTemplate { +// message: "Cannot create a recipe without being logged in", +// user: None, +// } +// .to_response()) +// } +// }; + +// let recipe_id = connection.create_recipe_async(user.id).await?; +// let recipes = connection.get_all_recipe_titles_async().await?; +// let user_id = user.id; + +// Ok(EditRecipeTemplate { +// user: Some(user), +// current_recipe_id: Some(recipe_id), +// recipes, +// current_recipe: model::Recipe::empty(recipe_id, user_id), +// } +// .to_response()) +// } ///// MESSAGE ///// #[derive(Template)] -#[template(path = "message_base.html")] -struct MessageBaseTemplate<'a> { +#[template(path = "message_without_user.html")] +struct MessageWithoutUser<'a> { message: &'a str, } @@ -308,22 +192,20 @@ struct SignUpFormTemplate { message_password: String, } -#[get("/signup")] +#[debug_handler] pub async fn sign_up_get( - req: HttpRequest, - connection: web::Data, -) -> impl Responder { - let user = get_current_user(&req, connection.clone()).await; - SignUpFormTemplate { + Extension(user): Extension>, +) -> Result { + Ok(SignUpFormTemplate { user, email: String::new(), message: String::new(), message_email: String::new(), message_password: String::new(), - } + }) } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct SignUpFormData { email: String, password_1: String, @@ -339,21 +221,22 @@ enum SignUpError { UnableSendEmail, } -#[post("/signup")] +#[debug_handler(state = AppState)] pub async fn sign_up_post( - req: HttpRequest, - form: web::Form, - connection: web::Data, - config: web::Data, -) -> Result { + Host(host): Host, + State(connection): State, + State(config): State, + Extension(user): Extension>, + Form(form_data): Form, +) -> Result { fn error_response( error: SignUpError, - form: &web::Form, + form_data: &SignUpFormData, user: Option, - ) -> Result { + ) -> Result { Ok(SignUpFormTemplate { user, - email: form.email.clone(), + email: form_data.email.clone(), message_email: match error { SignUpError::InvalidEmail => "Invalid email", _ => "", @@ -373,40 +256,35 @@ pub async fn sign_up_post( } .to_string(), } - .to_response()) + .into_response()) } - let user = get_current_user(&req, connection.clone()).await; - // Validation of email and password. - if let common::utils::EmailValidation::NotValid = common::utils::validate_email(&form.email) { - return error_response(SignUpError::InvalidEmail, &form, user); + if let common::utils::EmailValidation::NotValid = + common::utils::validate_email(&form_data.email) + { + return error_response(SignUpError::InvalidEmail, &form_data, user); } - if form.password_1 != form.password_2 { - return error_response(SignUpError::PasswordsNotEqual, &form, user); + if form_data.password_1 != form_data.password_2 { + return error_response(SignUpError::PasswordsNotEqual, &form_data, user); } if let common::utils::PasswordValidation::TooShort = - common::utils::validate_password(&form.password_1) + common::utils::validate_password(&form_data.password_1) { - return error_response(SignUpError::InvalidPassword, &form, user); + return error_response(SignUpError::InvalidPassword, &form_data, user); } match connection - .sign_up_async(&form.email, &form.password_1) + .sign_up(&form_data.email, &form_data.password_1) .await { Ok(db::SignUpResult::UserAlreadyExists) => { - error_response(SignUpError::UserAlreadyExists, &form, user) + error_response(SignUpError::UserAlreadyExists, &form_data, user) } Ok(db::SignUpResult::UserCreatedWaitingForValidation(token)) => { let url = { - let host = req - .headers() - .get(header::HOST) - .map(|v| v.to_str().unwrap_or_default()) - .unwrap_or_default(); let port: Option = 'p: { let split_port: Vec<&str> = host.split(':').collect(); if split_port.len() == 2 { @@ -427,60 +305,61 @@ pub async fn sign_up_post( ) }; - let email = form.email.clone(); - - match web::block(move || { - email::send_validation( - &url, - &email, - &token, - &config.smtp_login, - &config.smtp_password, - ) - }) - .await? + println!("{}", &url); + + let email = form_data.email.clone(); + match email::send_validation( + &url, + &email, + &token, + &config.smtp_relay_address, + &config.smtp_login, + &config.smtp_password, + ) + .await { - Ok(()) => Ok(HttpResponse::Found() - .insert_header((header::LOCATION, "/signup_check_email")) - .finish()), - Err(error) => { - error!("Email validation error: {}", error); - error_response(SignUpError::UnableSendEmail, &form, user) + Ok(()) => Ok(MessageTemplate { + user, + message: "An email has been sent, follow the link to validate your account.", + } + .into_response()), + Err(_) => { + // error!("Email validation error: {}", error); // TODO: log + error_response(SignUpError::UnableSendEmail, &form_data, user) } } } - Err(error) => { - error!("Signup database error: {}", error); - error_response(SignUpError::DatabaseError, &form, user) + Err(_) => { + // error!("Signup database error: {}", error); + error_response(SignUpError::DatabaseError, &form_data, user) } } } -#[get("/signup_check_email")] -pub async fn sign_up_check_email( - req: HttpRequest, - connection: web::Data, -) -> impl Responder { - let user = get_current_user(&req, connection.clone()).await; - MessageTemplate { - user, - message: "An email has been sent, follow the link to validate your account.", - } -} - -#[get("/validation")] +#[debug_handler] pub async fn sign_up_validation( - req: HttpRequest, - query: web::Query>, - connection: web::Data, -) -> Result { - let (client_ip, client_user_agent) = get_ip_and_user_agent(&req); - let user = get_current_user(&req, connection.clone()).await; - - match query.get("token") { + State(connection): State, + Extension(user): Extension>, + ConnectInfo(addr): ConnectInfo, + Query(query): Query>, + headers: HeaderMap, +) -> Result<(CookieJar, impl IntoResponse)> { + let mut jar = CookieJar::from_headers(&headers); + if user.is_some() { + return Ok(( + jar, + MessageTemplate { + user, + message: "User already exists", + }, + )); + } + let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr); + match query.get("validation_token") { + // 'validation_token' exists only when a user tries to validate a new account. Some(token) => { match connection - .validation_async( + .validation( token, Duration::seconds(consts::VALIDATION_TOKEN_DURATION), &client_ip, @@ -490,43 +369,39 @@ pub async fn sign_up_validation( { db::ValidationResult::Ok(token, user_id) => { let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token); - let user = match connection.load_user(user_id) { - Ok(user) => Some(user), - Err(error) => { - error!("Error retrieving user by id: {}", error); - None - } - }; - - let mut response = MessageTemplate { - user, - message: "Email validation successful, your account has been created", - } - .to_response(); - - if let Err(error) = response.add_cookie(&cookie) { - error!("Unable to set cookie after validation: {}", error); - }; - - Ok(response) - } - db::ValidationResult::ValidationExpired => Ok(MessageTemplate { - user, - message: "The validation has expired. Try to sign up again.", - } - .to_response()), - db::ValidationResult::UnknownUser => Ok(MessageTemplate { - user, - message: "Validation error.", + jar = jar.add(cookie); + let user = connection.load_user(user_id).await?; + Ok(( + jar, + MessageTemplate { + user, + message: "Email validation successful, your account has been created", + }, + )) } - .to_response()), + db::ValidationResult::ValidationExpired => Ok(( + jar, + MessageTemplate { + user, + message: "The validation has expired. Try to sign up again", + }, + )), + db::ValidationResult::UnknownUser => Ok(( + jar, + MessageTemplate { + user, + message: "Validation error. Try to sign up again", + }, + )), } } - None => Ok(MessageTemplate { - user, - message: &format!("No token provided"), - } - .to_response()), + None => Ok(( + jar, + MessageTemplate { + user, + message: "Validation error", + }, + )), } } @@ -540,109 +415,89 @@ struct SignInFormTemplate { message: String, } -#[get("/signin")] +#[debug_handler] pub async fn sign_in_get( - req: HttpRequest, - connection: web::Data, -) -> impl Responder { - let user = get_current_user(&req, connection.clone()).await; - SignInFormTemplate { + Extension(user): Extension>, +) -> Result { + Ok(SignInFormTemplate { user, email: String::new(), message: String::new(), - } + }) } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct SignInFormData { email: String, password: String, } -enum SignInError { - AccountNotValidated, - AuthenticationFailed, -} - -#[post("/signin")] +#[debug_handler] pub async fn sign_in_post( - req: HttpRequest, - form: web::Form, - connection: web::Data, -) -> Result { - fn error_response( - error: SignInError, - form: &web::Form, - user: Option, - ) -> Result { - Ok(SignInFormTemplate { - user, - email: form.email.clone(), - message: match error { - SignInError::AccountNotValidated => "This account must be validated first", - SignInError::AuthenticationFailed => "Wrong email or password", - } - .to_string(), - } - .to_response()) - } - - let user = get_current_user(&req, connection.clone()).await; - let (client_ip, client_user_agent) = get_ip_and_user_agent(&req); + ConnectInfo(addr): ConnectInfo, + State(connection): State, + Extension(user): Extension>, + headers: HeaderMap, + Form(form_data): Form, +) -> Result<(CookieJar, Response)> { + let jar = CookieJar::from_headers(&headers); + let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr); match connection - .sign_in_async(&form.email, &form.password, &client_ip, &client_user_agent) - .await + .sign_in( + &form_data.email, + &form_data.password, + &client_ip, + &client_user_agent, + ) + .await? { - Ok(db::SignInResult::AccountNotValidated) => { - error_response(SignInError::AccountNotValidated, &form, user) - } - Ok(db::SignInResult::UserNotFound) | Ok(db::SignInResult::WrongPassword) => { - error_response(SignInError::AuthenticationFailed, &form, user) - } - Ok(db::SignInResult::Ok(token, user_id)) => { + db::SignInResult::AccountNotValidated => Ok(( + jar, + SignInFormTemplate { + user, + email: form_data.email, + message: "This account must be validated first".to_string(), + } + .into_response(), + )), + db::SignInResult::UserNotFound | db::SignInResult::WrongPassword => Ok(( + jar, + SignInFormTemplate { + user, + email: form_data.email, + message: "Wrong email or password".to_string(), + } + .into_response(), + )), + db::SignInResult::Ok(token, _user_id) => { let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token); - let mut response = HttpResponse::Found() - .insert_header((header::LOCATION, "/")) - .finish(); - if let Err(error) = response.add_cookie(&cookie) { - error!("Unable to set cookie after sign in: {}", error); - }; - Ok(response) - } - Err(error) => { - error!("Signin error: {}", error); - error_response(SignInError::AuthenticationFailed, &form, user) + Ok((jar.add(cookie), Redirect::to("/").into_response())) } } } ///// SIGN OUT ///// -#[get("/signout")] -pub async fn sign_out(req: HttpRequest, connection: web::Data) -> impl Responder { - let mut response = HttpResponse::Found() - .insert_header((header::LOCATION, "/")) - .finish(); - - if let Some(token_cookie) = req.cookie(consts::COOKIE_AUTH_TOKEN_NAME) { - if let Err(error) = connection.sign_out_async(token_cookie.value()).await { - error!("Unable to sign out: {}", error); - }; - - if let Err(error) = - response.add_removal_cookie(&Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, "")) - { - error!("Unable to set a removal cookie after sign out: {}", error); - }; - }; - response +#[debug_handler] +pub async fn sign_out( + State(connection): State, + req: Request, +) -> Result<(CookieJar, Redirect)> { + let mut jar = CookieJar::from_headers(req.headers()); + if let Some(token_cookie) = jar.get(consts::COOKIE_AUTH_TOKEN_NAME) { + let token = token_cookie.value().to_string(); + jar = jar.remove(consts::COOKIE_AUTH_TOKEN_NAME); + connection.sign_out(&token).await?; + } + Ok((jar, Redirect::to("/"))) } -pub async fn not_found(req: HttpRequest, connection: web::Data) -> impl Responder { - let user = get_current_user(&req, connection.clone()).await; - MessageTemplate { - user, +///// 404 ///// + +#[debug_handler] +pub async fn not_found() -> Result { + Ok(MessageWithoutUser { message: "404: Not found", - } + }) } diff --git a/backend/src/services/webapi.rs b/backend/src/services/webapi.rs index c72d96d..93836a2 100644 --- a/backend/src/services/webapi.rs +++ b/backend/src/services/webapi.rs @@ -1,38 +1,38 @@ -use actix_web::{ - http::{header, header::ContentType, StatusCode}, - post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder, -}; -use log::{debug, error, info, log_enabled, Level}; -use ron::de::from_bytes; +// use actix_web::{ +// http::{header, header::ContentType, StatusCode}, +// post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder, +// }; +// use log::{debug, error, info, log_enabled, Level}; +// use ron::de::from_bytes; -use super::Result; -use crate::data::{asynchronous, db}; +// use super::Result; +// use crate::data::db; -#[put("/ron-api/recipe/set-title")] -pub async fn set_recipe_title( - req: HttpRequest, - body: web::Bytes, - connection: web::Data, -) -> Result { - let ron_req: common::ron_api::SetRecipeTitle = from_bytes(&body)?; - connection - .set_recipe_title_async(ron_req.recipe_id, &ron_req.title) - .await?; - Ok(HttpResponse::Ok().finish()) -} +// #[put("/ron-api/recipe/set-title")] +// pub async fn set_recipe_title( +// req: HttpRequest, +// body: web::Bytes, +// connection: web::Data, +// ) -> Result { +// let ron_req: common::ron_api::SetRecipeTitle = from_bytes(&body)?; +// connection +// .set_recipe_title_async(ron_req.recipe_id, &ron_req.title) +// .await?; +// Ok(HttpResponse::Ok().finish()) +// } -#[put("/ron-api/recipe/set-description")] -pub async fn set_recipe_description( - req: HttpRequest, - body: web::Bytes, - connection: web::Data, -) -> Result { - let ron_req: common::ron_api::SetRecipeDescription = from_bytes(&body)?; - connection - .set_recipe_description_async(ron_req.recipe_id, &ron_req.description) - .await?; - Ok(HttpResponse::Ok().finish()) -} +// #[put("/ron-api/recipe/set-description")] +// pub async fn set_recipe_description( +// req: HttpRequest, +// body: web::Bytes, +// connection: web::Data, +// ) -> Result { +// let ron_req: common::ron_api::SetRecipeDescription = from_bytes(&body)?; +// connection +// .set_recipe_description_async(ron_req.recipe_id, &ron_req.description) +// .await?; +// Ok(HttpResponse::Ok().finish()) +// } // #[put("/ron-api/recipe/add-image)] // #[put("/ron-api/recipe/rm-photo")] diff --git a/backend/src/utils.rs b/backend/src/utils.rs index f8d41af..3885ce9 100644 --- a/backend/src/utils.rs +++ b/backend/src/utils.rs @@ -1,11 +1,20 @@ -use log::error; - -pub fn unwrap_print_err(r: Result) -> T -where - E: std::fmt::Debug, -{ - if let Err(ref error) = r { - error!("{:?}", error); - } - r.unwrap() +use std::net::SocketAddr; + +use axum::http::HeaderMap; + +use crate::consts; + +pub fn get_ip_and_user_agent(headers: &HeaderMap, remote_address: SocketAddr) -> (String, String) { + let ip = match headers.get(consts::REVERSE_PROXY_IP_HTTP_FIELD) { + Some(v) => v.to_str().unwrap_or_default().to_string(), + None => remote_address.to_string(), + }; + + let user_agent = headers + .get(axum::http::header::USER_AGENT) + .map(|v| v.to_str().unwrap_or_default()) + .unwrap_or_default() + .to_string(); + + (ip, user_agent) } diff --git a/backend/templates/base_with_header.html b/backend/templates/base_with_header.html index 9cff88c..ea4a50d 100644 --- a/backend/templates/base_with_header.html +++ b/backend/templates/base_with_header.html @@ -2,7 +2,7 @@ {% block body_container %}
- ~~ Recettes de cuisine ~~ + {% include "title.html" %} {% match user %} {% when Some with (user) %} diff --git a/backend/templates/message.html b/backend/templates/message.html index cccc4e4..95d3874 100644 --- a/backend/templates/message.html +++ b/backend/templates/message.html @@ -1,7 +1,6 @@ {% extends "base_with_header.html" %} {% block main_container %} - -{{ message|markdown }} - + {{ message|markdown }} + Go to home {% endblock %} \ No newline at end of file diff --git a/backend/templates/message_base.html b/backend/templates/message_without_user.html similarity index 53% rename from backend/templates/message_base.html rename to backend/templates/message_without_user.html index f73b85f..ee42a33 100644 --- a/backend/templates/message_base.html +++ b/backend/templates/message_without_user.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% block body_container %} -{% include "title.html" %} -{{ message|markdown }} + {% include "title.html" %} + {{ message|markdown }} {% endblock %} \ No newline at end of file diff --git a/backend/templates/title.html b/backend/templates/title.html index 2a4715c..33f7014 100644 --- a/backend/templates/title.html +++ b/backend/templates/title.html @@ -1 +1 @@ -

~~ Recettes de cuisine ~~

\ No newline at end of file +~~ Recettes de cuisine ~~ \ No newline at end of file diff --git a/check_cargo_dependencies_upgrade.nu b/check_cargo_dependencies_upgrade.nu new file mode 100644 index 0000000..7ada4aa --- /dev/null +++ b/check_cargo_dependencies_upgrade.nu @@ -0,0 +1,2 @@ +# It needs cargo-edit: https://crates.io/crates/cargo-edit . +cargo upgrade --dry-run --verbose \ No newline at end of file diff --git a/common/src/lib.rs b/common/src/lib.rs index 94cf1a5..2a89f96 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,2 +1,2 @@ +pub mod ron_api; pub mod utils; -pub mod ron_api; \ No newline at end of file diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index 566e430..1d50b12 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -1,4 +1,3 @@ -use ron::de::from_reader; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone)] diff --git a/common/src/utils.rs b/common/src/utils.rs index 94b8c38..9322ed6 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -1,5 +1,5 @@ -use regex::Regex; use lazy_static::lazy_static; +use regex::Regex; pub enum EmailValidation { Ok, @@ -7,11 +7,18 @@ pub enum EmailValidation { } lazy_static! { - static ref EMAIL_REGEX: Regex = Regex::new(r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})").expect("Error parsing email regex"); + static ref EMAIL_REGEX: Regex = Regex::new( + r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})" + ) + .expect("Error parsing email regex"); } pub fn validate_email(email: &str) -> EmailValidation { - if EMAIL_REGEX.is_match(email) { EmailValidation::Ok } else { EmailValidation::NotValid } + if EMAIL_REGEX.is_match(email) { + EmailValidation::Ok + } else { + EmailValidation::NotValid + } } pub enum PasswordValidation { @@ -20,5 +27,9 @@ pub enum PasswordValidation { } pub fn validate_password(password: &str) -> PasswordValidation { - if password.len() < 8 { PasswordValidation::TooShort } else { PasswordValidation::Ok } -} \ No newline at end of file + if password.len() < 8 { + PasswordValidation::TooShort + } else { + PasswordValidation::Ok + } +} diff --git a/deploy.nu b/deploy.nu index 2e46351..3cb718a 100644 --- a/deploy.nu +++ b/deploy.nu @@ -1,18 +1,20 @@ def main [host: string, destination: string, ssh_key: path] { let ssh_args = [-i $ssh_key $host] let scp_args = [-r -i $ssh_key] - let target = "aarch64-unknown-linux-gnu" # For raspberry pi zero 1: "arm-unknown-linux-gnueabihf" + + # For raspberry pi zero 1: "arm-unknown-linux-gnueabihf" + let target = "aarch64-unknown-linux-gnu" def invoke_ssh [command: list] { let args = $ssh_args ++ $command print $"Executing: ssh ($args)" - ssh $args + ssh ...$args } def copy_ssh [source: string, destination: string] { let args = $scp_args ++ [$source $"($host):($destination)"] print $"Executing: scp ($args)" - scp $args + scp ...$args } cargo build --target $target --release @@ -25,4 +27,3 @@ def main [host: string, destination: string, ssh_key: path] { invoke_ssh [sudo systemctl start recipes] print "Deployment finished" } - diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 56462f2..c555ff7 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -11,21 +11,29 @@ crate-type = ["cdylib"] default = ["console_error_panic_hook"] [dependencies] -common = {path = "../common"} +common = { path = "../common" } wasm-bindgen = "0.2" -web-sys = {version = "0.3", features = ['console', 'Document', 'Element', 'HtmlElement', 'Node', 'Window', 'Location']} +web-sys = { version = "0.3", features = [ + 'console', + 'Document', + 'Element', + 'HtmlElement', + 'Node', + 'Window', + 'Location', +] } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. -console_error_panic_hook = {version = "0.1", optional = true} +console_error_panic_hook = { version = "0.1", optional = true } # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # compared to the default allocator's ~10K. It is slower than the default # allocator, however. -wee_alloc = {version = "0.4", optional = true} +wee_alloc = { version = "0.4", optional = true } # [dev-dependencies] # wasm-bindgen-test = "0.3" -- 2.45.2