From e5107575f543799154deb43ac3638f705abe2cfa Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 11 Aug 2024 00:07:53 -0700 Subject: [PATCH 01/44] feat: create base of push daemon Signed-off-by: IAmTomahawkx --- Cargo.lock | 444 +++++++++++++----- Cargo.toml | 2 +- crates/core/config/Revolt.toml | 38 +- crates/core/config/src/lib.rs | 30 +- crates/core/database/src/events/mod.rs | 1 + crates/core/database/src/events/rabbit.rs | 26 + .../database/src/models/messages/model.rs | 4 +- .../database/src/tasks/apple_notifications.rs | 14 +- crates/core/database/src/tasks/web_push.rs | 7 +- crates/core/models/src/v0/messages.rs | 13 +- crates/pushd/Cargo.toml | 29 ++ crates/pushd/src/consumers/mod.rs | 1 + crates/pushd/src/consumers/origin.rs | 89 ++++ crates/pushd/src/main.rs | 69 +++ 14 files changed, 609 insertions(+), 158 deletions(-) create mode 100644 crates/core/database/src/events/rabbit.rs create mode 100644 crates/pushd/Cargo.toml create mode 100644 crates/pushd/src/consumers/mod.rs create mode 100644 crates/pushd/src/consumers/origin.rs create mode 100644 crates/pushd/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index fae184ef6..a59d66235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,41 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "amqp_serde" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "787581044ca08ad61cdb3a4e21afba087fb8cdba3bb3e23ce69a7d091808014d" +dependencies = [ + "bytes 1.5.0", + "serde", + "serde_bytes_ng", +] + +[[package]] +name = "amqprs" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4173302f657e24f1b914edc992549077ec7f4971599e1aea42a01b17eb6e079" +dependencies = [ + "amqp_serde", + "async-trait", + "bytes 1.5.0", + "serde", + "serde_bytes", + "tokio 1.39.2", + "tracing", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -128,7 +163,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -183,7 +218,7 @@ dependencies = [ "futures-lite", "once_cell", "tokio 0.2.25", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -238,8 +273,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", - "quote 1.0.26", - "syn 2.0.15", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -302,7 +337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -314,13 +349,13 @@ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", - "quote 1.0.26", - "syn 1.0.107", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -358,7 +393,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -636,7 +671,7 @@ dependencies = [ "instant", "once_cell", "thiserror", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -648,7 +683,7 @@ dependencies = [ "cached_proc_macro_types", "darling 0.14.4", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -734,7 +769,7 @@ dependencies = [ "futures-core", "memchr", "pin-project-lite 0.2.13", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.7.2", ] @@ -898,7 +933,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -971,7 +1006,7 @@ dependencies = [ "fnv", "ident_case", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "strsim", "syn 1.0.107", ] @@ -985,7 +1020,7 @@ dependencies = [ "fnv", "ident_case", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "strsim", "syn 1.0.107", ] @@ -997,7 +1032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core 0.13.4", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1008,7 +1043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1037,7 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16a2561fd313df162315935989dceb8c99db4ee1933358270a57a3cfb8c957f3" dependencies = [ "crossbeam-queue", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -1084,7 +1119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aed3b3c608dc56cf36c45fe979d04eda51242e6703d8d0bb03426ef7c41db6a" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "synstructure", ] @@ -1096,7 +1131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1117,7 +1152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" dependencies = [ "devise_core", - "quote 1.0.26", + "quote 1.0.36", ] [[package]] @@ -1129,7 +1164,7 @@ dependencies = [ "bitflags", "proc-macro2", "proc-macro2-diagnostics", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1266,7 +1301,7 @@ checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ "heck", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1286,7 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1469,7 +1504,7 @@ dependencies = [ "redis-protocol", "semver 1.0.9", "socket2 0.5.5", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-stream", "tokio-util 0.7.2", "url", @@ -1553,7 +1588,7 @@ checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" dependencies = [ "futures-channel", "futures-task", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -1563,7 +1598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1654,7 +1689,7 @@ checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" dependencies = [ "proc-macro-error", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -1730,7 +1765,7 @@ dependencies = [ "http 0.2.7", "indexmap 1.9.3", "slab", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.7.2", "tracing", ] @@ -1749,7 +1784,7 @@ dependencies = [ "http 1.1.0", "indexmap 2.0.1", "slab", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.7.2", "tracing", ] @@ -1817,6 +1852,12 @@ dependencies = [ "libc", ] +[[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" @@ -1971,7 +2012,7 @@ dependencies = [ "itoa", "pin-project-lite 0.2.13", "socket2 0.4.4", - "tokio 1.35.1", + "tokio 1.39.2", "tower-service", "tracing", "want", @@ -1990,8 +2031,8 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "pin-project-lite 0.2.13", - "smallvec", - "tokio 1.35.1", + "smallvec 1.13.2", + "tokio 1.39.2", "want", ] @@ -2007,7 +2048,7 @@ dependencies = [ "hyper-util", "rustls 0.22.4", "rustls-pki-types", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-rustls 0.25.0", "tower-service", "webpki-roots 0.26.3", @@ -2022,7 +2063,7 @@ dependencies = [ "bytes 1.5.0", "hyper 0.14.19", "native-tls", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-native-tls", ] @@ -2040,7 +2081,7 @@ dependencies = [ "hyper 1.3.1", "pin-project-lite 0.2.13", "socket2 0.5.5", - "tokio 1.35.1", + "tokio 1.39.2", "tower", "tower-service", "tracing", @@ -2405,7 +2446,7 @@ dependencies = [ "serde", "serde_json", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.11", ] [[package]] @@ -2453,6 +2494,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2468,6 +2518,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "md-5" version = "0.10.1" @@ -2500,7 +2556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49e30813093f757be5cf21e50389a24dc7dbb22c49f23b7e8f51d69b508a5ffa" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -2527,13 +2583,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2551,9 +2608,9 @@ dependencies = [ "log", "metrics", "thiserror", - "tokio 1.35.1", + "tokio 1.39.2", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.11", ] [[package]] @@ -2603,7 +2660,7 @@ dependencies = [ "strsim", "take_mut", "thiserror", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-rustls 0.23.4", "tokio-util 0.7.2", "trust-dns-proto", @@ -2628,7 +2685,7 @@ dependencies = [ "memchr", "mime", "spin 0.9.3", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.6.10", "version_check", ] @@ -2692,7 +2749,7 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", - "smallvec", + "smallvec 1.13.2", "zeroize", ] @@ -2733,7 +2790,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -2763,7 +2820,7 @@ checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ "proc-macro-crate", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -2775,8 +2832,8 @@ checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate", "proc-macro2", - "quote 1.0.26", - "syn 2.0.15", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -2828,7 +2885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -2872,6 +2929,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "p256" version = "0.11.1" @@ -2919,7 +2985,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec", + "smallvec 1.13.2", "windows-sys 0.36.1", ] @@ -2963,7 +3029,7 @@ checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -3040,7 +3106,7 @@ dependencies = [ "pest", "pest_meta", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -3071,7 +3137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -3180,7 +3246,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "version_check", ] @@ -3192,7 +3258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "version_check", ] @@ -3212,7 +3278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "version_check", "yansi", @@ -3232,6 +3298,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "pushd" +version = "0.1.0" +dependencies = [ + "amqprs", + "async-trait", + "authifier", + "fcm", + "iso8601-timestamp 0.2.11", + "revolt-config", + "revolt-database", + "revolt-models", + "revolt_a2", + "revolt_optional_struct", + "serde", + "serde_json", + "tokio 1.39.2", + "tracing", + "tracing-subscriber 0.1.6", + "ulid 1.0.0", + "web-push", +] + [[package]] name = "querystring" version = "1.1.0" @@ -3252,9 +3341,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.26" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3426,7 +3515,7 @@ dependencies = [ "pin-project-lite 0.2.13", "ryu", "sha1_smol", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.7.2", "url", ] @@ -3446,7 +3535,7 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.13", "ryu", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-util 0.7.2", "url", ] @@ -3506,7 +3595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -3572,7 +3661,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-native-tls", "url", "wasm-bindgen", @@ -3807,7 +3896,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -3856,7 +3945,7 @@ checksum = "cc6620569d8ac8f0a1690fcca13f488503807a60e96ebf729749b59aca1dbef9" dependencies = [ "darling 0.13.4", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "rocket_http", "syn 1.0.107", ] @@ -3954,7 +4043,7 @@ dependencies = [ "state", "tempfile", "time 0.3.17", - "tokio 1.35.1", + "tokio 1.39.2", "tokio-stream", "tokio-util 0.7.2", "ubyte", @@ -3988,7 +4077,7 @@ dependencies = [ "glob", "indexmap 1.9.3", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "rocket_http", "syn 1.0.107", "unicode-xid 0.2.3", @@ -4040,11 +4129,11 @@ dependencies = [ "pin-project-lite 0.2.13", "ref-cast", "serde", - "smallvec", + "smallvec 1.13.2", "stable-pattern", "state", "time 0.3.17", - "tokio 1.35.1", + "tokio 1.39.2", "uncased", ] @@ -4085,7 +4174,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.3", "signature", - "smallvec", + "smallvec 1.13.2", "subtle", "zeroize", ] @@ -4250,7 +4339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "serde_derive_internals", "syn 1.0.107", ] @@ -4361,7 +4450,7 @@ dependencies = [ "sentry-debug-images", "sentry-panic", "sentry-tracing", - "tokio 1.35.1", + "tokio 1.39.2", "ureq", ] @@ -4434,7 +4523,7 @@ dependencies = [ "sentry-backtrace", "sentry-core", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.11", ] [[package]] @@ -4456,9 +4545,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.205" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" dependencies = [ "serde_derive", ] @@ -4472,15 +4561,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes_ng" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb0ebce8684e2253f964e8b6ce51f0ccc6666bbb448fb4a6788088bda6544b6" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.205" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" dependencies = [ "proc-macro2", - "quote 1.0.26", - "syn 2.0.15", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -4490,7 +4588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -4536,7 +4634,7 @@ checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling 0.13.4", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -4635,6 +4733,15 @@ dependencies = [ "futures-io", ] +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -4692,6 +4799,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.5.3" @@ -4741,18 +4854,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.15" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "unicode-ident", ] @@ -4772,7 +4885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "unicode-xid 0.2.3", ] @@ -4836,7 +4949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -4915,32 +5028,31 @@ dependencies = [ [[package]] name = "tokio" -version = "1.35.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes 1.5.0", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite 0.2.13", "signal-hook-registry", "socket2 0.5.5", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", - "quote 1.0.26", - "syn 2.0.15", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -4950,7 +5062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -4960,7 +5072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls 0.20.6", - "tokio 1.35.1", + "tokio 1.39.2", "webpki", ] @@ -4972,7 +5084,7 @@ checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls 0.22.4", "rustls-pki-types", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -4983,7 +5095,7 @@ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite 0.2.13", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -4997,7 +5109,7 @@ dependencies = [ "futures-sink", "log", "pin-project-lite 0.2.13", - "tokio 1.35.1", + "tokio 1.39.2", ] [[package]] @@ -5011,7 +5123,7 @@ dependencies = [ "futures-io", "futures-sink", "pin-project-lite 0.2.13", - "tokio 1.35.1", + "tokio 1.39.2", "tracing", ] @@ -5046,7 +5158,7 @@ dependencies = [ "futures-util", "pin-project", "pin-project-lite 0.2.13", - "tokio 1.35.1", + "tokio 1.39.2", "tower-layer", "tower-service", ] @@ -5082,8 +5194,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", - "quote 1.0.26", - "syn 2.0.15", + "quote 1.0.36", + "syn 2.0.58", ] [[package]] @@ -5117,18 +5229,35 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192ca16595cdd0661ce319e8eede9c975f227cdaabc4faaefdc256f43d852e45" +dependencies = [ + "ansi_term 0.11.0", + "chrono", + "lazy_static", + "matchers 0.0.1", + "owning_ref", + "regex", + "smallvec 0.6.14", + "tracing-core", + "tracing-log", +] + [[package]] name = "tracing-subscriber" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ - "ansi_term", + "ansi_term 0.12.1", "lazy_static", - "matchers", + "matchers 0.1.0", "regex", "sharded-slab", - "smallvec", + "smallvec 1.13.2", "thread_local", "tracing", "tracing-core", @@ -5153,10 +5282,10 @@ dependencies = [ "lazy_static", "log", "rand 0.8.5", - "smallvec", + "smallvec 1.13.2", "thiserror", "tinyvec", - "tokio 1.35.1", + "tokio 1.39.2", "url", ] @@ -5174,9 +5303,9 @@ dependencies = [ "lru-cache", "parking_lot", "resolv-conf", - "smallvec", + "smallvec 1.13.2", "thiserror", - "tokio 1.35.1", + "tokio 1.39.2", "trust-dns-proto", ] @@ -5212,7 +5341,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", ] @@ -5473,7 +5602,7 @@ dependencies = [ "lazy_static", "proc-macro-error", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "regex", "syn 1.0.107", "validator_types", @@ -5583,7 +5712,7 @@ dependencies = [ "lazy_static", "log", "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "wasm-bindgen-shared", ] @@ -5606,7 +5735,7 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ - "quote 1.0.26", + "quote 1.0.36", "wasm-bindgen-macro-support", ] @@ -5617,7 +5746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", - "quote 1.0.26", + "quote 1.0.36", "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -5754,7 +5883,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -5763,21 +5901,43 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", + "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", + "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -5790,6 +5950,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -5802,6 +5968,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.36.1" @@ -5814,6 +5992,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -5826,12 +6010,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -5844,6 +6040,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winreg" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index b55327166..9f1f73641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/delta", "crates/bonfire", "crates/core/*"] +members = ["crates/delta", "crates/bonfire", "crates/core/*", "crates/pushd"] [patch.crates-io] # mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" } diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 2321f062f..7ec6a8bc5 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -11,6 +11,12 @@ january = "http://local.revolt.chat/january" voso_legacy = "" voso_legacy_ws = "" +[rabbit] +host = "127.0.0.1" +port = 5672 +username = "guest" +password = "guest" + [api] [api.registration] @@ -25,19 +31,6 @@ from_address = "" # port = 587 # use_tls = true -[api.vapid] -private_key = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo" -public_key = "BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=" - -[api.fcm] -api_key = "" - -[api.apn] -sandbox = false -pkcs8 = "" -key_id = "" -team_id = "" - [api.security] authifier_shield_key = "" voso_legacy_token = "" @@ -50,6 +43,25 @@ hcaptcha_sitekey = "" [api.workers] max_concurrent_connections = 50 +[pushd] +exchange = "revolt.notifications" + +[pushd.vapid] +queue = "notifications.vapid" +private_key = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo" +public_key = "BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=" + +[pushd.fcm] +queue = "notifications.fcm" +api_key = "" + +[pushd.apn] +sandbox = false +queue = "notifications.apn" +pkcs8 = "" +key_id = "" +team_id = "" + [features] webhooks_enabled = false diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 5c49f2387..d54711667 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -39,6 +39,14 @@ pub struct Database { pub redis: String, } +#[derive(Deserialize, Debug, Clone)] +pub struct Rabbit { + pub host: String, + pub port: u16, + pub username: String, + pub password: String, +} + #[derive(Deserialize, Debug, Clone)] pub struct Hosts { pub app: String, @@ -67,18 +75,21 @@ pub struct ApiSmtp { } #[derive(Deserialize, Debug, Clone)] -pub struct ApiVapid { +pub struct PushVapid { + pub queue: String, pub private_key: String, pub public_key: String, } #[derive(Deserialize, Debug, Clone)] -pub struct ApiFcm { +pub struct PushFcm { + pub queue: String, pub api_key: String, } #[derive(Deserialize, Debug, Clone)] -pub struct ApiApn { +pub struct PushApn { + pub queue: String, pub sandbox: bool, pub pkcs8: String, pub key_id: String, @@ -108,13 +119,18 @@ pub struct ApiWorkers { pub struct Api { pub registration: ApiRegistration, pub smtp: ApiSmtp, - pub vapid: ApiVapid, - pub fcm: ApiFcm, - pub apn: ApiApn, pub security: ApiSecurity, pub workers: ApiWorkers, } +#[derive(Deserialize, Debug, Clone)] +pub struct Pushd { + pub exchange: String, + pub vapid: PushVapid, + pub fcm: PushFcm, + pub apn: PushApn, +} + #[derive(Deserialize, Debug, Clone)] pub struct GlobalLimits { pub group_size: usize, @@ -171,8 +187,10 @@ pub struct Sentry { #[derive(Deserialize, Debug, Clone)] pub struct Settings { pub database: Database, + pub rabbit: Rabbit, pub hosts: Hosts, pub api: Api, + pub pushd: Pushd, pub features: Features, pub sentry: Sentry, } diff --git a/crates/core/database/src/events/mod.rs b/crates/core/database/src/events/mod.rs index c07f47e0f..608984357 100644 --- a/crates/core/database/src/events/mod.rs +++ b/crates/core/database/src/events/mod.rs @@ -1,2 +1,3 @@ pub mod client; +pub mod rabbit; pub mod server; diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs new file mode 100644 index 000000000..8e7ffed4f --- /dev/null +++ b/crates/core/database/src/events/rabbit.rs @@ -0,0 +1,26 @@ +use revolt_models::v0::PushNotification; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct MessageSentNotification { + pub notification: PushNotification, + pub users: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct BasicPayload { + pub title: String, + pub body: String, +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type", content = "data")] +pub enum PayloadKind { + MessageNotification(PushNotification), +} + +#[derive(Serialize, Deserialize)] +pub struct PayloadToService { + pub notification: PayloadKind, + pub token: String, +} diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 154430553..2d3ef99fe 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, ops::Deref}; use indexmap::{IndexMap, IndexSet}; use iso8601_timestamp::Timestamp; @@ -480,7 +480,7 @@ impl Message { PushNotification::from( self.clone().into_model(user, member), Some(author), - channel.id(), + channel.to_owned().into(), ) .await, ) diff --git a/crates/core/database/src/tasks/apple_notifications.rs b/crates/core/database/src/tasks/apple_notifications.rs index e390bad15..c9ef5d5c7 100644 --- a/crates/core/database/src/tasks/apple_notifications.rs +++ b/crates/core/database/src/tasks/apple_notifications.rs @@ -185,30 +185,30 @@ async fn get_badge_count(db: &Database, user: &str) -> Option { /// Start a new worker pub async fn worker(db: Database) { let config = config().await; - if config.api.apn.pkcs8.is_empty() - || config.api.apn.key_id.is_empty() - || config.api.apn.team_id.is_empty() + if config.pushd.apn.pkcs8.is_empty() + || config.pushd.apn.key_id.is_empty() + || config.pushd.apn.team_id.is_empty() { eprintln!("Missing APN keys."); return; } - let endpoint = if config.api.apn.sandbox { + let endpoint = if config.pushd.apn.sandbox { Endpoint::Sandbox } else { Endpoint::Production }; let pkcs8 = engine::general_purpose::STANDARD - .decode(config.api.apn.pkcs8) + .decode(config.pushd.apn.pkcs8) .expect("valid `pcks8`"); let client_config = ClientConfig::new(endpoint); let client = Client::token( &mut Cursor::new(pkcs8), - config.api.apn.key_id, - config.api.apn.team_id, + config.pushd.apn.key_id, + config.pushd.apn.team_id, client_config, ) .expect("could not create APN client"); diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index 7e24e2af5..6929f34da 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -54,14 +54,14 @@ pub async fn worker(db: Database) { let config = config().await; let web_push_client = IsahcWebPushClient::new().unwrap(); - let fcm_client = if config.api.fcm.api_key.is_empty() { + let fcm_client = if config.pushd.fcm.api_key.is_empty() { None } else { Some(fcm::Client::new()) }; let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD - .decode(config.api.vapid.private_key) + .decode(config.pushd.vapid.private_key) .expect("valid `VAPID_PRIVATE_KEY`"); loop { @@ -82,6 +82,7 @@ pub async fn worker(db: Database) { timestamp: _, url: _, message: _, + channel: _, } = &task.payload; let mut notification = fcm::NotificationBuilder::new(); @@ -93,7 +94,7 @@ pub async fn worker(db: Database) { let notification = notification.finalize(); let mut message_builder = - fcm::MessageBuilder::new(&config.api.fcm.api_key, &sub.auth); + fcm::MessageBuilder::new(&config.pushd.fcm.api_key, &sub.auth); message_builder.notification(notification); if let Err(err) = client.send(message_builder.finalize()).await { diff --git a/crates/core/models/src/v0/messages.rs b/crates/core/models/src/v0/messages.rs index ef67b09fd..b8c53aeed 100644 --- a/crates/core/models/src/v0/messages.rs +++ b/crates/core/models/src/v0/messages.rs @@ -13,7 +13,7 @@ use rocket::{FromForm, FromFormField}; use iso8601_timestamp::Timestamp; -use super::{Embed, File, Member, MessageWebhook, User, Webhook, RE_COLOUR}; +use super::{Channel, Embed, File, Member, MessageWebhook, User, Webhook, RE_COLOUR}; pub static RE_MENTION: Lazy = Lazy::new(|| Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap()); @@ -209,6 +209,8 @@ auto_derived!( pub url: String, /// The message object itself, to send to clients for processing pub message: Message, + /// The channel object itself, for clients to process + pub channel: Channel, } /// Representation of a text embed before it is sent. @@ -369,7 +371,7 @@ auto_derived!( /// Optional fields on message pub enum FieldsMessage { - Pinned + Pinned, } ); @@ -442,7 +444,7 @@ impl From for String { impl PushNotification { /// Create a new notification from a given message, author and channel ID - pub async fn from(msg: Message, author: Option>, channel_id: &str) -> Self { + pub async fn from(msg: Message, author: Option>, channel: Channel) -> Self { let config = config().await; let icon = if let Some(author) = &author { @@ -496,10 +498,11 @@ impl PushNotification { icon, image, body, - tag: channel_id.to_string(), + tag: channel.id().to_string(), timestamp, - url: format!("{}/channel/{}/{}", config.hosts.app, channel_id, msg.id), + url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id), message: msg, + channel, } } } diff --git a/crates/pushd/Cargo.toml b/crates/pushd/Cargo.toml new file mode 100644 index 000000000..352ed661f --- /dev/null +++ b/crates/pushd/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "pushd" +version = "0.1.0" +edition = "2021" + +[dependencies] +revolt-config = { version = "0.7.15", path = "../core/config" } +revolt-database = { version = "0.7.15", path = "../core/database" } +revolt-models = { version = "0.7.15", path = "../core/models", features = [ + "validator", +] } + +tracing = "0.1" +tracing-subscriber = "0.1" +amqprs = { version = "1.7.0", features = ["traces"] } +fcm = "0.9.2" +web-push = "0.10.0" +revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } +tokio = "1.39.2" +async-trait = "0.1.81" +ulid = "1.0.0" + +authifier = "1.0.8" + +#serialization +serde_json = "1" +revolt_optional_struct = "0.2.0" +serde = { version = "1", features = ["derive"] } +iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] } diff --git a/crates/pushd/src/consumers/mod.rs b/crates/pushd/src/consumers/mod.rs new file mode 100644 index 000000000..5d9fa5f9e --- /dev/null +++ b/crates/pushd/src/consumers/mod.rs @@ -0,0 +1 @@ +pub mod origin; diff --git a/crates/pushd/src/consumers/origin.rs b/crates/pushd/src/consumers/origin.rs new file mode 100644 index 000000000..66451e46c --- /dev/null +++ b/crates/pushd/src/consumers/origin.rs @@ -0,0 +1,89 @@ +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + consumer::AsyncConsumer, + BasicProperties, Deliver, +}; +use async_trait::async_trait; +use revolt_database::{events::rabbit::*, Database}; + +pub struct OriginConsumer { + db: Database, + authifier_db: authifier::Database, + config: revolt_config::Settings, +} + +impl OriginConsumer { + pub fn new( + db: Database, + authifier_db: authifier::Database, + config: revolt_config::Settings, + ) -> OriginConsumer { + OriginConsumer { + db, + authifier_db, + config, + } + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for OriginConsumer { + /// This consumer handles delegating messages into their respective platform queues. + async fn consume( + &mut self, + channel: &Channel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: MessageSentNotification = serde_json::from_str(content.as_str()).unwrap(); + + if let Ok(sessions) = self + .authifier_db + .find_sessions_with_subscription(&payload.users) + .await + { + for session in sessions { + if let Some(sub) = session.subscription { + let sendable = serde_json::to_string(&PayloadToService { + notification: PayloadKind::MessageNotification( + payload.notification.clone(), + ), + token: sub.auth, + }) + .unwrap(); + + let args: BasicPublishArguments; + + if sub.endpoint == "apn" { + args = BasicPublishArguments::new( + self.config.pushd.exchange.as_str(), + self.config.pushd.apn.queue.as_str(), + ) + .finish(); + } else if sub.endpoint == "fcm" { + args = BasicPublishArguments::new( + self.config.pushd.exchange.as_str(), + self.config.pushd.fcm.queue.as_str(), + ) + .finish(); + } else { + // web push (vapid) + args = BasicPublishArguments::new( + self.config.pushd.exchange.as_str(), + self.config.pushd.vapid.queue.as_str(), + ) + .finish(); + } + + channel + .basic_publish(BasicProperties::default(), sendable.into(), args) + .await + .unwrap(); + } + } + } + } +} diff --git a/crates/pushd/src/main.rs b/crates/pushd/src/main.rs new file mode 100644 index 000000000..23d76df97 --- /dev/null +++ b/crates/pushd/src/main.rs @@ -0,0 +1,69 @@ +use amqprs::{ + callbacks::DefaultChannelCallback, + channel::{BasicConsumeArguments, QueueBindArguments, QueueDeclareArguments}, + connection::{Connection, OpenConnectionArguments}, +}; +use revolt_config::config; +use tokio::sync::Notify; + +mod consumers; + +#[tokio::main(flavor = "multi_thread", worker_threads = 2)] +async fn main() { + let config = config().await; + + // Setup database + let db = revolt_database::DatabaseInfo::Auto.connect().await.unwrap(); + let authifier: authifier::Database; + + if let Some(client) = match &db { + revolt_database::Database::Reference(_) => None, + revolt_database::Database::MongoDb(mongo) => Some(mongo), + } { + authifier = + authifier::Database::MongoDb(authifier::database::MongoDb(client.database("revolt"))); + } else { + panic!("Mongo is not in use, can't connect via authifier!") + } + + let connection = Connection::open(&OpenConnectionArguments::new( + "127.0.0.1", + 5672, + "guest", + "guest", + )) + .await + .unwrap(); + + let channel = connection.open_channel(None).await.unwrap(); + + let args = QueueDeclareArguments::new("notifications.ingest.message") + .durable(true) + .finish(); + + let (queue_name, _, _) = channel.queue_declare(args).await.unwrap().unwrap(); + + channel + .queue_bind(QueueBindArguments::new( + &queue_name, + "revolt.notifications", + "notifications", + )) + .await + .unwrap(); + + let args = BasicConsumeArguments::new(&queue_name, "basic_consumer") + .manual_ack(true) + .finish(); + + channel + .basic_consume( + consumers::origin::OriginConsumer::new(db.clone(), authifier.clone(), config), + args, + ) + .await + .unwrap(); + + let guard = Notify::new(); + guard.notified().await; +} From 299d0f6f4ead4ebcfd4b513464bdda93f0f3c892 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 20 Aug 2024 10:08:50 -0700 Subject: [PATCH 02/44] Add outbound senders --- Cargo.lock | 9 +- crates/core/config/Revolt.toml | 7 +- crates/core/config/src/lib.rs | 1 + crates/core/database/src/events/rabbit.rs | 5 + crates/core/models/src/v0/channels.rs | 10 + crates/pushd/Cargo.toml | 1 + crates/pushd/src/consumers/base.rs | 0 crates/pushd/src/consumers/mod.rs | 1 + crates/pushd/src/consumers/origin.rs | 49 ++--- crates/pushd/src/consumers/outbound/apn.rs | 210 +++++++++++++++++++ crates/pushd/src/consumers/outbound/fcm.rs | 100 +++++++++ crates/pushd/src/consumers/outbound/mod.rs | 3 + crates/pushd/src/consumers/outbound/vapid.rs | 130 ++++++++++++ crates/pushd/src/main.rs | 89 ++++++-- 14 files changed, 564 insertions(+), 51 deletions(-) create mode 100644 crates/pushd/src/consumers/base.rs create mode 100644 crates/pushd/src/consumers/outbound/apn.rs create mode 100644 crates/pushd/src/consumers/outbound/fcm.rs create mode 100644 crates/pushd/src/consumers/outbound/mod.rs create mode 100644 crates/pushd/src/consumers/outbound/vapid.rs diff --git a/Cargo.lock b/Cargo.lock index a59d66235..13b838d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3305,6 +3305,7 @@ dependencies = [ "amqprs", "async-trait", "authifier", + "base64 0.22.1", "fcm", "iso8601-timestamp 0.2.11", "revolt-config", @@ -4545,9 +4546,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.205" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] @@ -4572,9 +4573,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote 1.0.36", diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 7ec6a8bc5..6fa8886e4 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -45,19 +45,20 @@ max_concurrent_connections = 50 [pushd] exchange = "revolt.notifications" +message_queue = "notifications.origin.message" [pushd.vapid] -queue = "notifications.vapid" +queue = "notifications.outbound.vapid" private_key = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo" public_key = "BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=" [pushd.fcm] -queue = "notifications.fcm" +queue = "notifications.outbound.fcm" api_key = "" [pushd.apn] sandbox = false -queue = "notifications.apn" +queue = "notifications.outbound.apn" pkcs8 = "" key_id = "" team_id = "" diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index d54711667..2e08ef0ff 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -126,6 +126,7 @@ pub struct Api { #[derive(Deserialize, Debug, Clone)] pub struct Pushd { pub exchange: String, + pub message_queue: String, pub vapid: PushVapid, pub fcm: PushFcm, pub apn: PushApn, diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs index 8e7ffed4f..de1216e7f 100644 --- a/crates/core/database/src/events/rabbit.rs +++ b/crates/core/database/src/events/rabbit.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use revolt_models::v0::PushNotification; use serde::{Deserialize, Serialize}; @@ -22,5 +24,8 @@ pub enum PayloadKind { #[derive(Serialize, Deserialize)] pub struct PayloadToService { pub notification: PayloadKind, + pub user_id: String, + pub session_id: String, pub token: String, + pub extras: HashMap, } diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs index dfabb2af7..e3adca9e5 100644 --- a/crates/core/models/src/v0/channels.rs +++ b/crates/core/models/src/v0/channels.rs @@ -306,4 +306,14 @@ impl Channel { | Channel::VoiceChannel { id, .. } => id, } } + + pub fn name(&self) -> &str { + match self { + Channel::DirectMessage { .. } => "DMs", + Channel::SavedMessages { .. } => "Saved Messages", + Channel::TextChannel { name, .. } + | Channel::Group { name, .. } + | Channel::VoiceChannel { name, .. } => name, + } + } } diff --git a/crates/pushd/Cargo.toml b/crates/pushd/Cargo.toml index 352ed661f..5e14e49b8 100644 --- a/crates/pushd/Cargo.toml +++ b/crates/pushd/Cargo.toml @@ -27,3 +27,4 @@ serde_json = "1" revolt_optional_struct = "0.2.0" serde = { version = "1", features = ["derive"] } iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] } +base64 = "0.22.1" diff --git a/crates/pushd/src/consumers/base.rs b/crates/pushd/src/consumers/base.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/pushd/src/consumers/mod.rs b/crates/pushd/src/consumers/mod.rs index 5d9fa5f9e..3b9db3880 100644 --- a/crates/pushd/src/consumers/mod.rs +++ b/crates/pushd/src/consumers/mod.rs @@ -1 +1,2 @@ pub mod origin; +pub mod outbound; diff --git a/crates/pushd/src/consumers/origin.rs b/crates/pushd/src/consumers/origin.rs index 66451e46c..5e84d87a4 100644 --- a/crates/pushd/src/consumers/origin.rs +++ b/crates/pushd/src/consumers/origin.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use amqprs::{ channel::{BasicPublishArguments, Channel}, consumer::AsyncConsumer, @@ -6,29 +8,21 @@ use amqprs::{ use async_trait::async_trait; use revolt_database::{events::rabbit::*, Database}; -pub struct OriginConsumer { +pub struct OriginMessageConsumer { + #[allow(dead_code)] db: Database, authifier_db: authifier::Database, - config: revolt_config::Settings, } -impl OriginConsumer { - pub fn new( - db: Database, - authifier_db: authifier::Database, - config: revolt_config::Settings, - ) -> OriginConsumer { - OriginConsumer { - db, - authifier_db, - config, - } +impl OriginMessageConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> OriginMessageConsumer { + OriginMessageConsumer { db, authifier_db } } } #[allow(unused_variables)] #[async_trait] -impl AsyncConsumer for OriginConsumer { +impl AsyncConsumer for OriginMessageConsumer { /// This consumer handles delegating messages into their respective platform queues. async fn consume( &mut self, @@ -45,41 +39,48 @@ impl AsyncConsumer for OriginConsumer { .find_sessions_with_subscription(&payload.users) .await { + let config = revolt_config::config().await; for session in sessions { if let Some(sub) = session.subscription { - let sendable = serde_json::to_string(&PayloadToService { + let mut sendable = PayloadToService { notification: PayloadKind::MessageNotification( payload.notification.clone(), ), token: sub.auth, - }) - .unwrap(); + user_id: session.user_id, + session_id: session.id, + extras: HashMap::new(), + }; let args: BasicPublishArguments; if sub.endpoint == "apn" { args = BasicPublishArguments::new( - self.config.pushd.exchange.as_str(), - self.config.pushd.apn.queue.as_str(), + config.pushd.exchange.as_str(), + config.pushd.apn.queue.as_str(), ) .finish(); } else if sub.endpoint == "fcm" { args = BasicPublishArguments::new( - self.config.pushd.exchange.as_str(), - self.config.pushd.fcm.queue.as_str(), + config.pushd.exchange.as_str(), + config.pushd.fcm.queue.as_str(), ) .finish(); } else { // web push (vapid) args = BasicPublishArguments::new( - self.config.pushd.exchange.as_str(), - self.config.pushd.vapid.queue.as_str(), + config.pushd.exchange.as_str(), + config.pushd.vapid.queue.as_str(), ) .finish(); + sendable.extras.insert("p265dh".to_string(), sub.p256dh); + sendable.extras.insert("endpoint".to_string(), sub.endpoint); } + let payload = serde_json::to_string(&sendable).unwrap(); + channel - .basic_publish(BasicProperties::default(), sendable.into(), args) + .basic_publish(BasicProperties::default(), payload.into(), args) .await .unwrap(); } diff --git a/crates/pushd/src/consumers/outbound/apn.rs b/crates/pushd/src/consumers/outbound/apn.rs new file mode 100644 index 000000000..58449fac0 --- /dev/null +++ b/crates/pushd/src/consumers/outbound/apn.rs @@ -0,0 +1,210 @@ +use std::io::Cursor; + +use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; +use async_trait::async_trait; +use base64::{ + engine::{self}, + Engine as _, +}; +use revolt_a2::{ + request::{ + notification::{DefaultAlert, NotificationOptions}, + payload::{APSAlert, APSSound, PayloadLike, APS}, + }, + Client, ClientConfig, Endpoint, Error, ErrorBody, ErrorReason, Priority, PushType, Response, +}; +use revolt_database::{events::rabbit::*, Database}; +use revolt_models::v0::{Channel, Message, PushNotification}; +use serde::Serialize; + +// region: payload + +#[derive(Serialize, Debug)] +struct MessagePayload<'a> { + aps: APS<'a>, + #[serde(skip_serializing)] + options: NotificationOptions<'a>, + #[serde(skip_serializing)] + device_token: &'a str, + + message: &'a Message, + url: &'a str, + #[serde(rename = "camelCase")] + author_avatar: &'a str, + #[serde(rename = "camelCase")] + author_display_name: &'a str, + #[serde(rename = "camelCase")] + channel_name: &'a str, +} + +impl<'a> PayloadLike for MessagePayload<'a> { + fn get_device_token(&self) -> &'a str { + self.device_token + } + fn get_options(&self) -> &NotificationOptions { + &self.options + } +} + +// region: consumer + +pub struct ApnsOutboundConsumer { + #[allow(dead_code)] + db: Database, + client: Client, +} + +impl ApnsOutboundConsumer { + fn format_title(&self, notification: &PushNotification) -> String { + // ideally this changes depending on context + // in a server, it would look like "Sendername, #channelname in servername" + // in a group, it would look like "Sendername in groupname" + // in a dm it should just be "Sendername". + // not sure how feasible all those are given the PushNotification object as it currently stands. + + match ¬ification.channel { + Channel::DirectMessage { .. } => notification.author.clone(), + Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), + Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { + format!("{} in #{}", notification.author, name) + } + _ => "Unknown".to_string(), + } + } + + async fn get_badge_count(&self, user: &str) -> Option { + if let Ok(unreads) = self.db.fetch_unreads(user).await { + let mut mention_count = 0; + for channel in unreads { + if let Some(mentions) = channel.mentions { + mention_count += mentions.len() as u32 + } + } + + return Some(mention_count); + } + None + } +} + +impl ApnsOutboundConsumer { + pub async fn new(db: Database) -> Result { + let config = revolt_config::config().await; + + if config.pushd.apn.pkcs8.is_empty() + || config.pushd.apn.key_id.is_empty() + || config.pushd.apn.team_id.is_empty() + { + return Err("Missing APN keys."); + } + + let endpoint = if config.pushd.apn.sandbox { + Endpoint::Sandbox + } else { + Endpoint::Production + }; + + let pkcs8 = engine::general_purpose::STANDARD + .decode(config.pushd.apn.pkcs8.clone()) + .expect("valid `pcks8`"); + + let client_config = ClientConfig::new(endpoint); + + let client = Client::token( + &mut Cursor::new(pkcs8), + config.pushd.apn.key_id.clone(), + config.pushd.apn.team_id.clone(), + client_config, + ) + .expect("could not create APN client"); + + Ok(ApnsOutboundConsumer { db, client }) + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for ApnsOutboundConsumer { + async fn consume( + &mut self, + channel: &AmqpChannel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: PayloadToService = serde_json::from_str(content.as_str()).unwrap(); + + let payload_options = NotificationOptions { + apns_id: None, + apns_push_type: Some(PushType::Alert), + apns_expiration: None, + apns_priority: Some(Priority::High), + apns_topic: Some("chat.revolt.app"), + apns_collapse_id: None, + }; + + let resp: Result; + + match payload.notification { + PayloadKind::MessageNotification(alert) => { + let title = self.format_title(&alert); + let apn_payload = MessagePayload { + aps: APS { + alert: Some(APSAlert::Default(DefaultAlert { + title: Some(&title), + subtitle: None, + body: Some(&alert.body), + title_loc_key: None, + title_loc_args: None, + action_loc_key: None, + loc_key: None, + loc_args: None, + launch_image: None, + })), + badge: self.get_badge_count(&payload.user_id).await, + sound: Some(APSSound::Sound("default")), + thread_id: Some(alert.channel.id()), + content_available: None, + category: None, + mutable_content: Some(1), + url_args: None, + }, + device_token: &payload.token, + options: payload_options.clone(), + message: &alert.message, + url: &alert.url, + author_avatar: &alert.icon, + author_display_name: &alert.author, + channel_name: alert.channel.name(), + }; + + resp = self.client.send(apn_payload).await; + } + } + + if let Err(err) = resp { + match err { + Error::ResponseError(Response { + error: + Some(ErrorBody { + reason: ErrorReason::BadDeviceToken | ErrorReason::Unregistered, + .. + }), + .. + }) => { + if let Err(err) = self + .db + .remove_push_subscription_by_session_id(&payload.session_id) + .await + { + revolt_config::capture_error(&err); + } + } + err => { + revolt_config::capture_error(&err); + } + } + } + } +} diff --git a/crates/pushd/src/consumers/outbound/fcm.rs b/crates/pushd/src/consumers/outbound/fcm.rs new file mode 100644 index 000000000..9f9eaa7f5 --- /dev/null +++ b/crates/pushd/src/consumers/outbound/fcm.rs @@ -0,0 +1,100 @@ +use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; + +use async_trait::async_trait; +use fcm::{Client, FcmError, FcmResponse, MessageBuilder}; +use revolt_database::{events::rabbit::*, Database}; +use revolt_models::v0::{Channel, PushNotification}; + +pub struct FcmOutboundConsumer { + db: Database, + client: Client, +} + +impl FcmOutboundConsumer { + fn format_title(&self, notification: &PushNotification) -> String { + // ideally this changes depending on context + // in a server, it would look like "Sendername, #channelname in servername" + // in a group, it would look like "Sendername in groupname" + // in a dm it should just be "Sendername". + // not sure how feasible all those are given the PushNotification object as it currently stands. + + match ¬ification.channel { + Channel::DirectMessage { .. } => notification.author.clone(), + Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), + Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { + format!("{} in #{}", notification.author, name) + } + _ => "Unknown".to_string(), + } + } +} + +impl FcmOutboundConsumer { + pub async fn new(db: Database) -> Result { + let config = revolt_config::config().await; + + if config.pushd.fcm.api_key.is_empty() { + return Err("No FCM key present"); + } + + Ok(FcmOutboundConsumer { + db, + client: Client::new(), + }) + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for FcmOutboundConsumer { + async fn consume( + &mut self, + channel: &AmqpChannel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: PayloadToService = serde_json::from_str(content.as_str()).unwrap(); + + let config = revolt_config::config().await; + let resp: Result; + + match payload.notification { + PayloadKind::MessageNotification(alert) => { + let title = self.format_title(&alert); + + let mut notification = fcm::NotificationBuilder::new(); + notification.title(title.as_str()); + notification.icon(&alert.icon); + notification.body(&alert.body); + notification.tag(alert.channel.id()); + // TODO: expand support for fields + let notification = notification.finalize(); + + let mut message_builder = + MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); + message_builder.notification(notification); + + resp = self.client.send(message_builder.finalize()).await; + } + } + + if let Err(err) = resp { + match err { + FcmError::Unauthorized => { + if let Err(err) = self + .db + .remove_push_subscription_by_session_id(&payload.session_id) + .await + { + revolt_config::capture_error(&err); + } + } + err => { + revolt_config::capture_error(&err); + } + } + } + } +} diff --git a/crates/pushd/src/consumers/outbound/mod.rs b/crates/pushd/src/consumers/outbound/mod.rs new file mode 100644 index 000000000..cfff0a268 --- /dev/null +++ b/crates/pushd/src/consumers/outbound/mod.rs @@ -0,0 +1,3 @@ +pub mod apn; +pub mod fcm; +pub mod vapid; diff --git a/crates/pushd/src/consumers/outbound/vapid.rs b/crates/pushd/src/consumers/outbound/vapid.rs new file mode 100644 index 000000000..91a743a95 --- /dev/null +++ b/crates/pushd/src/consumers/outbound/vapid.rs @@ -0,0 +1,130 @@ +use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; + +use async_trait::async_trait; +use base64::{ + engine::{self}, + Engine as _, +}; +use revolt_database::{events::rabbit::*, Database}; +use revolt_models::v0::{Channel, PushNotification}; +use web_push::{ + ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, + WebPushClient, WebPushError, WebPushMessageBuilder, +}; + +pub struct VapidOutboundConsumer { + db: Database, + client: IsahcWebPushClient, + pkey: Vec, +} + +impl VapidOutboundConsumer { + fn format_title(&self, notification: &PushNotification) -> String { + // ideally this changes depending on context + // in a server, it would look like "Sendername, #channelname in servername" + // in a group, it would look like "Sendername in groupname" + // in a dm it should just be "Sendername". + // not sure how feasible all those are given the PushNotification object as it currently stands. + + match ¬ification.channel { + Channel::DirectMessage { .. } => notification.author.clone(), + Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), + Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { + format!("{} in #{}", notification.author, name) + } + _ => "Unknown".to_string(), + } + } +} + +impl VapidOutboundConsumer { + pub async fn new(db: Database) -> Result { + let config = revolt_config::config().await; + + if config.pushd.vapid.private_key.is_empty() | config.pushd.vapid.public_key.is_empty() { + return Err("No Vapid keys present"); + } + + let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD + .decode(config.pushd.vapid.private_key) + .expect("valid `VAPID_PRIVATE_KEY`"); + + Ok(VapidOutboundConsumer { + db, + client: IsahcWebPushClient::new().unwrap(), + pkey: web_push_private_key, + }) + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for VapidOutboundConsumer { + async fn consume( + &mut self, + channel: &AmqpChannel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: PayloadToService = serde_json::from_str(content.as_str()).unwrap(); + + let config = revolt_config::config().await; + + let subscription = SubscriptionInfo { + endpoint: payload.extras.get("endpoint").unwrap().clone(), + keys: SubscriptionKeys { + auth: payload.token, + p256dh: payload.extras.get("p256dh").unwrap().clone(), + }, + }; + + match payload.notification { + PayloadKind::MessageNotification(alert) => { + let title = self.format_title(&alert); + match VapidSignatureBuilder::from_pem( + std::io::Cursor::new(&self.pkey), + &subscription, + ) { + Ok(sig_builder) => match sig_builder.build() { + Ok(signature) => { + let mut builder = WebPushMessageBuilder::new(&subscription); + builder.set_vapid_signature(signature); + + let alert_json = serde_json::to_string(&alert).unwrap(); + builder.set_payload(ContentEncoding::AesGcm, alert_json.as_bytes()); + + match builder.build() { + Ok(msg) => { + if let Err(err) = self.client.send(msg).await { + if err == WebPushError::Unauthorized { + if let Err(err) = self + .db + .remove_push_subscription_by_session_id( + &payload.session_id, + ) + .await + { + revolt_config::capture_error(&err); + } + } + } + } + Err(err) => { + revolt_config::capture_error(&err); + } + } + } + Err(err) => { + revolt_config::capture_error(&err); + } + }, + Err(err) => { + revolt_config::capture_error(&err); + } + } + } + } + } +} diff --git a/crates/pushd/src/main.rs b/crates/pushd/src/main.rs index 23d76df97..aede120a4 100644 --- a/crates/pushd/src/main.rs +++ b/crates/pushd/src/main.rs @@ -1,12 +1,16 @@ use amqprs::{ - callbacks::DefaultChannelCallback, channel::{BasicConsumeArguments, QueueBindArguments, QueueDeclareArguments}, connection::{Connection, OpenConnectionArguments}, + consumer::AsyncConsumer, }; -use revolt_config::config; +use revolt_config::{config, Settings}; use tokio::sync::Notify; mod consumers; +use consumers::{ + origin::OriginMessageConsumer, outbound::apn::ApnsOutboundConsumer, + outbound::fcm::FcmOutboundConsumer, outbound::vapid::VapidOutboundConsumer, +}; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { @@ -26,6 +30,62 @@ async fn main() { panic!("Mongo is not in use, can't connect via authifier!") } + let mut connections: Vec = Vec::new(); + + connections.push( + make_queue_and_consume( + &config, + &config.pushd.message_queue, + OriginMessageConsumer::new(db.clone(), authifier.clone()), + ) + .await, + ); + + if !config.pushd.apn.pkcs8.is_empty() { + connections.push( + make_queue_and_consume( + &config, + &config.pushd.apn.queue, + ApnsOutboundConsumer::new(db.clone()).await.unwrap(), + ) + .await, + ); + } + + if !config.pushd.fcm.api_key.is_empty() { + connections.push( + make_queue_and_consume( + &config, + &config.pushd.fcm.queue, + FcmOutboundConsumer::new(db.clone()).await.unwrap(), + ) + .await, + ) + } + + if !config.pushd.vapid.public_key.is_empty() { + connections.push( + make_queue_and_consume( + &config, + &config.pushd.fcm.queue, + VapidOutboundConsumer::new(db.clone()).await.unwrap(), + ) + .await, + ) + } + + let guard = Notify::new(); + guard.notified().await; + + for conn in connections { + conn.close().await.expect("Unable to close connection"); + } +} + +async fn make_queue_and_consume(config: &Settings, name: &str, consumer: F) -> Connection +where + F: AsyncConsumer + Send + 'static, +{ let connection = Connection::open(&OpenConnectionArguments::new( "127.0.0.1", 5672, @@ -37,33 +97,22 @@ async fn main() { let channel = connection.open_channel(None).await.unwrap(); - let args = QueueDeclareArguments::new("notifications.ingest.message") - .durable(true) - .finish(); - + let args = QueueDeclareArguments::new(name).durable(true).finish(); let (queue_name, _, _) = channel.queue_declare(args).await.unwrap().unwrap(); channel .queue_bind(QueueBindArguments::new( &queue_name, - "revolt.notifications", - "notifications", + &config.pushd.exchange, + name, )) .await .unwrap(); - let args = BasicConsumeArguments::new(&queue_name, "basic_consumer") - .manual_ack(true) + let args = BasicConsumeArguments::new(name, "basic_consumer") + .manual_ack(false) .finish(); - channel - .basic_consume( - consumers::origin::OriginConsumer::new(db.clone(), authifier.clone(), config), - args, - ) - .await - .unwrap(); - - let guard = Notify::new(); - guard.notified().await; + channel.basic_consume(consumer, args).await.unwrap(); + connection } From bc96bfb0e561ad80499e24bc928d19e02b6ad21a Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 21 Aug 2024 15:49:07 -0700 Subject: [PATCH 03/44] Make web_push send to rabbit instead (temp stuff) --- Cargo.lock | 2 + crates/core/database/Cargo.toml | 7 +- crates/core/database/src/tasks/web_push.rs | 276 ++++++++++++--------- crates/delta/src/routes/root.rs | 2 +- crates/pushd/Cargo.toml | 1 + crates/pushd/src/consumers/origin.rs | 79 +++++- crates/pushd/src/main.rs | 29 ++- 7 files changed, 262 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13b838d8d..052e6ad29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3307,6 +3307,7 @@ dependencies = [ "authifier", "base64 0.22.1", "fcm", + "isahc", "iso8601-timestamp 0.2.11", "revolt-config", "revolt-database", @@ -3731,6 +3732,7 @@ dependencies = [ name = "revolt-database" version = "0.7.15" dependencies = [ + "amqprs", "async-lock", "async-recursion", "async-std", diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 5ba887526..3460667c8 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -87,9 +87,10 @@ revolt_rocket_okapi = { version = "0.9.1", optional = true } # Notifications fcm = "0.9.2" web-push = "0.10.0" -revolt_a2 = { version = "0.10", default-features = false, features = [ - "ring", -] } +revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } # Authifier authifier = { version = "1.0.8" } + +# RabbitMQ +amqprs = { version = "1.7.0", features = ["traces"] } diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index 6929f34da..bcd8f6f7e 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -1,5 +1,10 @@ use std::collections::HashSet; +use amqprs::{ + channel::BasicPublishArguments, + connection::{Connection, OpenConnectionArguments}, + BasicProperties, +}; use authifier::Database; use base64::{ engine::{self}, @@ -10,13 +15,14 @@ use once_cell::sync::Lazy; use revolt_config::config; use revolt_models::v0::PushNotification; use revolt_presence::filter_online; -use serde_json::json; +use serde_json::{json, to_string}; use web_push::{ ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder, }; use super::apple_notifications; +use crate::events::rabbit::{self, MessageSentNotification}; /// Task information #[derive(Debug)] @@ -53,122 +59,166 @@ pub async fn queue(recipients: Vec, payload: PushNotification) { pub async fn worker(db: Database) { let config = config().await; - let web_push_client = IsahcWebPushClient::new().unwrap(); - let fcm_client = if config.pushd.fcm.api_key.is_empty() { - None - } else { - Some(fcm::Client::new()) - }; - - let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD - .decode(config.pushd.vapid.private_key) - .expect("valid `VAPID_PRIVATE_KEY`"); + // let web_push_client = IsahcWebPushClient::new().unwrap(); + // let fcm_client = if config.pushd.fcm.api_key.is_empty() { + // None + // } else { + // Some(fcm::Client::new()) + // }; + + // let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD + // .decode(config.pushd.vapid.private_key) + // .expect("valid `VAPID_PRIVATE_KEY`"); + + let conn = Connection::open(&OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + )) + .await + .expect("Failed to create the AMQP connection"); + + let channel = conn + .open_channel(None) + .await + .expect("Failed to create an AMQP channel"); + + let basic_properties = BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(); + + let publish_arguments = + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue); loop { let task = Q.pop().await; - if let Ok(sessions) = db.find_sessions_with_subscription(&task.recipients).await { - for session in sessions { - if let Some(sub) = session.subscription { - if sub.endpoint == "fcm" { - // Use Firebase Cloud Messaging - if let Some(client) = &fcm_client { - let PushNotification { - author, - icon, - image: _, - body, - tag, - timestamp: _, - url: _, - message: _, - channel: _, - } = &task.payload; - - let mut notification = fcm::NotificationBuilder::new(); - notification.title(author); - notification.icon(icon); - notification.body(body); - notification.tag(tag); - // TODO: expand support for fields - let notification = notification.finalize(); - - let mut message_builder = - fcm::MessageBuilder::new(&config.pushd.fcm.api_key, &sub.auth); - message_builder.notification(notification); - - if let Err(err) = client.send(message_builder.finalize()).await { - error!("Failed to send FCM notification! {:?}", err); - } else { - info!("Sent FCM notification to {:?}.", session.id); - } - } else { - info!("No FCM token was specified!"); - } - } else if sub.endpoint == "apn" { - apple_notifications::queue(apple_notifications::ApnJob::from_notification( - session.id, - session.user_id, - sub.auth, - &task.payload, - )) - .await; - } else { - // Use Web Push Standard - let subscription = SubscriptionInfo { - endpoint: sub.endpoint, - keys: SubscriptionKeys { - auth: sub.auth, - p256dh: sub.p256dh, - }, - }; - - match VapidSignatureBuilder::from_pem( - std::io::Cursor::new(&web_push_private_key), - &subscription, - ) { - Ok(sig_builder) => match sig_builder.build() { - Ok(signature) => { - let mut builder = WebPushMessageBuilder::new(&subscription); - builder.set_vapid_signature(signature); - - let payload = json!(task.payload).to_string(); - builder - .set_payload(ContentEncoding::AesGcm, payload.as_bytes()); - - match builder.build() { - Ok(msg) => match web_push_client.send(msg).await { - Ok(_) => { - info!( - "Sent Web Push notification to {:?}.", - session.id - ) - } - Err(err) => { - error!("Hit error sending Web Push! {:?}", err) - } - }, - Err(err) => { - error!( - "Failed to build message for {}! {:?}", - session.user_id, err - ) - } - } - } - Err(err) => error!( - "Failed to build signature for {}! {:?}", - session.user_id, err - ), - }, - Err(err) => error!( - "Failed to create signature builder for {}! {:?}", - session.user_id, err - ), - } - } - } - } + let payload = MessageSentNotification { + notification: task.payload, + users: task.recipients, + }; + + if let Err(err) = channel + .basic_publish( + basic_properties.clone(), + to_string(&payload).unwrap().into_bytes(), + publish_arguments.clone(), + ) + .await + { + revolt_config::capture_error(&err); + error!("Failed to send notification") + } else { + debug!( + "Sent message to {} on exchange {}", + config.pushd.message_queue, config.pushd.exchange + ); } + + // if let Ok(sessions) = db.find_sessions_with_subscription(&task.recipients).await { + // for session in sessions { + // if let Some(sub) = session.subscription { + // if sub.endpoint == "fcm" { + // // Use Firebase Cloud Messaging + // if let Some(client) = &fcm_client { + // let PushNotification { + // author, + // icon, + // image: _, + // body, + // tag, + // timestamp: _, + // url: _, + // message: _, + // channel: _, + // } = &task.payload; + + // let mut notification = fcm::NotificationBuilder::new(); + // notification.title(author); + // notification.icon(icon); + // notification.body(body); + // notification.tag(tag); + // // TODO: expand support for fields + // let notification = notification.finalize(); + + // let mut message_builder = + // fcm::MessageBuilder::new(&config.pushd.fcm.api_key, &sub.auth); + // message_builder.notification(notification); + + // if let Err(err) = client.send(message_builder.finalize()).await { + // error!("Failed to send FCM notification! {:?}", err); + // } else { + // info!("Sent FCM notification to {:?}.", session.id); + // } + // } else { + // info!("No FCM token was specified!"); + // } + // } else if sub.endpoint == "apn" { + // apple_notifications::queue(apple_notifications::ApnJob::from_notification( + // session.id, + // session.user_id, + // sub.auth, + // &task.payload, + // )) + // .await; + // } else { + // // Use Web Push Standard + // let subscription = SubscriptionInfo { + // endpoint: sub.endpoint, + // keys: SubscriptionKeys { + // auth: sub.auth, + // p256dh: sub.p256dh, + // }, + // }; + + // match VapidSignatureBuilder::from_pem( + // std::io::Cursor::new(&web_push_private_key), + // &subscription, + // ) { + // Ok(sig_builder) => match sig_builder.build() { + // Ok(signature) => { + // let mut builder = WebPushMessageBuilder::new(&subscription); + // builder.set_vapid_signature(signature); + + // let payload = json!(task.payload).to_string(); + // builder + // .set_payload(ContentEncoding::AesGcm, payload.as_bytes()); + + // match builder.build() { + // Ok(msg) => match web_push_client.send(msg).await { + // Ok(_) => { + // info!( + // "Sent Web Push notification to {:?}.", + // session.id + // ) + // } + // Err(err) => { + // error!("Hit error sending Web Push! {:?}", err) + // } + // }, + // Err(err) => { + // error!( + // "Failed to build message for {}! {:?}", + // session.user_id, err + // ) + // } + // } + // } + // Err(err) => error!( + // "Failed to build signature for {}! {:?}", + // session.user_id, err + // ), + // }, + // Err(err) => error!( + // "Failed to create signature builder for {}! {:?}", + // session.user_id, err + // ), + // } + // } + // } + // } + // } } } diff --git a/crates/delta/src/routes/root.rs b/crates/delta/src/routes/root.rs index 1e237bff6..a67c596a6 100644 --- a/crates/delta/src/routes/root.rs +++ b/crates/delta/src/routes/root.rs @@ -114,7 +114,7 @@ pub async fn root() -> Result> { }, ws: config.hosts.events, app: config.hosts.app, - vapid: config.api.vapid.public_key, + vapid: config.pushd.vapid.public_key, build: BuildInformation { commit_sha: option_env!("VERGEN_GIT_SHA") .unwrap_or_else(|| "") diff --git a/crates/pushd/Cargo.toml b/crates/pushd/Cargo.toml index 5e14e49b8..ac87b8274 100644 --- a/crates/pushd/Cargo.toml +++ b/crates/pushd/Cargo.toml @@ -15,6 +15,7 @@ tracing-subscriber = "0.1" amqprs = { version = "1.7.0", features = ["traces"] } fcm = "0.9.2" web-push = "0.10.0" +isahc = { optional = true, version = "1.7", features = ["json"] } revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } tokio = "1.39.2" async-trait = "0.1.81" diff --git a/crates/pushd/src/consumers/origin.rs b/crates/pushd/src/consumers/origin.rs index 5e84d87a4..6802be1d7 100644 --- a/crates/pushd/src/consumers/origin.rs +++ b/crates/pushd/src/consumers/origin.rs @@ -2,21 +2,87 @@ use std::collections::HashMap; use amqprs::{ channel::{BasicPublishArguments, Channel}, + connection::{Connection, OpenConnectionArguments}, consumer::AsyncConsumer, BasicProperties, Deliver, }; use async_trait::async_trait; use revolt_database::{events::rabbit::*, Database}; +use tracing::debug; pub struct OriginMessageConsumer { #[allow(dead_code)] db: Database, authifier_db: authifier::Database, + conn: Option, + channel: Option, } impl OriginMessageConsumer { pub fn new(db: Database, authifier_db: authifier::Database) -> OriginMessageConsumer { - OriginMessageConsumer { db, authifier_db } + OriginMessageConsumer { + db, + authifier_db, + conn: None, + channel: None, + } + } + + async fn make_channel(&mut self) { + let config = revolt_config::config().await; + + let args = OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + ); + self.conn = Some(amqprs::connection::Connection::open(&args).await.unwrap()); + + let _raw_channel = self + .conn + .as_ref() + .unwrap() + .open_channel(None) + .await + .unwrap(); + + self.channel = Some(_raw_channel); + } + + async fn publish_message( + &mut self, + payload: Vec, + args: BasicPublishArguments, + attempt: u8, + ) { + let routing_key = &args.routing_key.clone(); + if attempt > 3 { + panic!( + "Failed 3 attempts to send a message to queue {}", + routing_key + ); + } + if self.channel.is_none() { + self.make_channel().await; + } + + if let Some(chnl) = &self.channel { + //if let Err(err) = + chnl.basic_publish(BasicProperties::default(), payload.clone(), args.clone()) + .await + .unwrap(); + // { + // match err { + // Error::InternalChannelError(_) => { + // self.make_channel().await; + // self.publish_message(payload, args, attempt + 1).await; + // } + // _ => {} + // } + // } + debug!("Sent message to queue for target {}", routing_key); + } } } @@ -34,6 +100,8 @@ impl AsyncConsumer for OriginMessageConsumer { let content = String::from_utf8(content).unwrap(); let payload: MessageSentNotification = serde_json::from_str(content.as_str()).unwrap(); + debug!("Received message event on origin"); + if let Ok(sessions) = self .authifier_db .find_sessions_with_subscription(&payload.users) @@ -74,15 +142,14 @@ impl AsyncConsumer for OriginMessageConsumer { ) .finish(); sendable.extras.insert("p265dh".to_string(), sub.p256dh); - sendable.extras.insert("endpoint".to_string(), sub.endpoint); + sendable + .extras + .insert("endpoint".to_string(), sub.endpoint.clone()); } let payload = serde_json::to_string(&sendable).unwrap(); - channel - .basic_publish(BasicProperties::default(), payload.into(), args) - .await - .unwrap(); + self.publish_message(payload.into(), args, 1).await; } } } diff --git a/crates/pushd/src/main.rs b/crates/pushd/src/main.rs index aede120a4..446a19fd8 100644 --- a/crates/pushd/src/main.rs +++ b/crates/pushd/src/main.rs @@ -1,5 +1,5 @@ use amqprs::{ - channel::{BasicConsumeArguments, QueueBindArguments, QueueDeclareArguments}, + channel::{BasicConsumeArguments, Channel, QueueBindArguments, QueueDeclareArguments}, connection::{Connection, OpenConnectionArguments}, consumer::AsyncConsumer, }; @@ -11,6 +11,7 @@ use consumers::{ origin::OriginMessageConsumer, outbound::apn::ApnsOutboundConsumer, outbound::fcm::FcmOutboundConsumer, outbound::vapid::VapidOutboundConsumer, }; +use tracing::info; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { @@ -30,7 +31,7 @@ async fn main() { panic!("Mongo is not in use, can't connect via authifier!") } - let mut connections: Vec = Vec::new(); + let mut connections: Vec<(Channel, Connection)> = Vec::new(); connections.push( make_queue_and_consume( @@ -67,7 +68,7 @@ async fn main() { connections.push( make_queue_and_consume( &config, - &config.pushd.fcm.queue, + &config.pushd.vapid.queue, VapidOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, @@ -77,20 +78,25 @@ async fn main() { let guard = Notify::new(); guard.notified().await; - for conn in connections { + for (channel, conn) in connections { + channel.close().await.expect("Unable to close channel"); conn.close().await.expect("Unable to close connection"); } } -async fn make_queue_and_consume(config: &Settings, name: &str, consumer: F) -> Connection +async fn make_queue_and_consume( + config: &Settings, + name: &str, + consumer: F, +) -> (Channel, Connection) where F: AsyncConsumer + Send + 'static, { let connection = Connection::open(&OpenConnectionArguments::new( - "127.0.0.1", - 5672, - "guest", - "guest", + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, )) .await .unwrap(); @@ -109,10 +115,11 @@ where .await .unwrap(); - let args = BasicConsumeArguments::new(name, "basic_consumer") + let args = BasicConsumeArguments::new(name, "") .manual_ack(false) .finish(); channel.basic_consume(consumer, args).await.unwrap(); - connection + info!("Consuming queue {}", name); + (channel, connection) } From 71583ded6096bd11e81bf75148d23401fecbe8df Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 24 Aug 2024 23:09:13 -0700 Subject: [PATCH 04/44] feat: stability and friend requests --- Cargo.lock | 96 ++---------- crates/core/config/Revolt.toml | 3 + crates/core/config/src/lib.rs | 4 + crates/core/database/Cargo.toml | 2 +- crates/core/database/src/amqp/amqp.rs | 141 ++++++++++++++++++ crates/core/database/src/amqp/mod.rs | 1 + crates/core/database/src/events/rabbit.rs | 26 +++- crates/core/database/src/lib.rs | 5 +- .../core/database/src/models/users/model.rs | 13 +- crates/core/database/src/tasks/web_push.rs | 4 +- crates/delta/Cargo.toml | 3 + crates/delta/src/main.rs | 17 ++- crates/delta/src/routes/users/add_friend.rs | 5 +- .../src/routes/users/send_friend_request.rs | 7 +- crates/pushd/Cargo.toml | 6 +- .../src/consumers/inbound/fr_accepted.rs | 121 +++++++++++++++ .../src/consumers/inbound/fr_received.rs | 121 +++++++++++++++ .../{origin.rs => inbound/generic.rs} | 96 ++++-------- .../pushd/src/consumers/inbound/internal.rs | 63 ++++++++ crates/pushd/src/consumers/inbound/message.rs | 127 ++++++++++++++++ crates/pushd/src/consumers/inbound/mod.rs | 5 + crates/pushd/src/consumers/mod.rs | 2 +- crates/pushd/src/consumers/outbound/apn.rs | 117 ++++++++++++++- crates/pushd/src/consumers/outbound/fcm.rs | 65 ++++++++ crates/pushd/src/consumers/outbound/vapid.rs | 111 +++++++------- crates/pushd/src/main.rs | 43 +++++- 26 files changed, 983 insertions(+), 221 deletions(-) create mode 100644 crates/core/database/src/amqp/amqp.rs create mode 100644 crates/core/database/src/amqp/mod.rs create mode 100644 crates/pushd/src/consumers/inbound/fr_accepted.rs create mode 100644 crates/pushd/src/consumers/inbound/fr_received.rs rename crates/pushd/src/consumers/{origin.rs => inbound/generic.rs} (60%) create mode 100644 crates/pushd/src/consumers/inbound/internal.rs create mode 100644 crates/pushd/src/consumers/inbound/message.rs create mode 100644 crates/pushd/src/consumers/inbound/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 052e6ad29..13fa484fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,16 +112,6 @@ dependencies = [ "serde", "serde_bytes", "tokio 1.39.2", - "tracing", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", ] [[package]] @@ -2031,7 +2021,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "pin-project-lite 0.2.13", - "smallvec 1.13.2", + "smallvec", "tokio 1.39.2", "want", ] @@ -2446,7 +2436,7 @@ dependencies = [ "serde", "serde_json", "tracing", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -2494,15 +2484,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata", -] - [[package]] name = "matchers" version = "0.1.0" @@ -2518,12 +2499,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md-5" version = "0.10.1" @@ -2610,7 +2585,7 @@ dependencies = [ "thiserror", "tokio 1.39.2", "tracing", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -2749,7 +2724,7 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", - "smallvec 1.13.2", + "smallvec", "zeroize", ] @@ -2929,15 +2904,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "owning_ref" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "p256" version = "0.11.1" @@ -2985,7 +2951,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.13.2", + "smallvec", "windows-sys 0.36.1", ] @@ -3309,6 +3275,7 @@ dependencies = [ "fcm", "isahc", "iso8601-timestamp 0.2.11", + "log", "revolt-config", "revolt-database", "revolt-models", @@ -3317,8 +3284,6 @@ dependencies = [ "serde", "serde_json", "tokio 1.39.2", - "tracing", - "tracing-subscriber 0.1.6", "ulid 1.0.0", "web-push", ] @@ -3780,6 +3745,7 @@ dependencies = [ name = "revolt-delta" version = "0.7.15" dependencies = [ + "amqprs", "async-channel 1.6.1", "async-std", "authifier", @@ -4132,7 +4098,7 @@ dependencies = [ "pin-project-lite 0.2.13", "ref-cast", "serde", - "smallvec 1.13.2", + "smallvec", "stable-pattern", "state", "time 0.3.17", @@ -4177,7 +4143,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.3", "signature", - "smallvec 1.13.2", + "smallvec", "subtle", "zeroize", ] @@ -4526,7 +4492,7 @@ dependencies = [ "sentry-backtrace", "sentry-core", "tracing-core", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -4736,15 +4702,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -4802,12 +4759,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "state" version = "0.5.3" @@ -5232,35 +5183,18 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192ca16595cdd0661ce319e8eede9c975f227cdaabc4faaefdc256f43d852e45" -dependencies = [ - "ansi_term 0.11.0", - "chrono", - "lazy_static", - "matchers 0.0.1", - "owning_ref", - "regex", - "smallvec 0.6.14", - "tracing-core", - "tracing-log", -] - [[package]] name = "tracing-subscriber" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "lazy_static", - "matchers 0.1.0", + "matchers", "regex", "sharded-slab", - "smallvec 1.13.2", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -5285,7 +5219,7 @@ dependencies = [ "lazy_static", "log", "rand 0.8.5", - "smallvec 1.13.2", + "smallvec", "thiserror", "tinyvec", "tokio 1.39.2", @@ -5306,7 +5240,7 @@ dependencies = [ "lru-cache", "parking_lot", "resolv-conf", - "smallvec 1.13.2", + "smallvec", "thiserror", "tokio 1.39.2", "trust-dns-proto", diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 6fa8886e4..e79982f38 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -46,6 +46,9 @@ max_concurrent_connections = 50 [pushd] exchange = "revolt.notifications" message_queue = "notifications.origin.message" +fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted +fr_received_queue = "notifications.ingest.fr_received" # friend request received +generic_queue = "notifications.ingest.generic" # generic messages (title + body) [pushd.vapid] queue = "notifications.outbound.vapid" diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 2e08ef0ff..d2ade05c0 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -127,6 +127,10 @@ pub struct Api { pub struct Pushd { pub exchange: String, pub message_queue: String, + pub fr_accepted_queue: String, + pub fr_received_queue: String, + pub generic_queue: String, + pub vapid: PushVapid, pub fcm: PushFcm, pub apn: PushApn, diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 3460667c8..4442f7d81 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -93,4 +93,4 @@ revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } authifier = { version = "1.0.8" } # RabbitMQ -amqprs = { version = "1.7.0", features = ["traces"] } +amqprs = { version = "1.7.0" } diff --git a/crates/core/database/src/amqp/amqp.rs b/crates/core/database/src/amqp/amqp.rs new file mode 100644 index 000000000..50fadd714 --- /dev/null +++ b/crates/core/database/src/amqp/amqp.rs @@ -0,0 +1,141 @@ +use std::collections::HashSet; + +use crate::events::rabbit::*; +use crate::User; +use amqprs::channel::BasicPublishArguments; +use amqprs::BasicProperties; +use amqprs::{channel::Channel, connection::Connection}; +use revolt_models::v0::PushNotification; +use revolt_presence::filter_online; + +use serde_json::to_string; + +#[derive(Clone)] +pub struct AMQP { + #[allow(unused)] + connection: Connection, + channel: Channel, +} + +impl AMQP { + pub fn new(connection: Connection, channel: Channel) -> AMQP { + AMQP { + connection, + channel, + } + } + + pub async fn friend_request_accepted( + &self, + accepted_request_user: &User, + sent_request_user: &User, + ) -> Result<(), amqprs::error::Error> { + let config = revolt_config::config().await; + let payload = FRAcceptedPayload { + accepted_user: accepted_request_user.to_owned(), + user: sent_request_user.id.clone(), + }; + let payload = to_string(&payload).unwrap(); + + info!("Sending friend request accepted event: {}", &payload); + + self.channel + .basic_publish( + BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(), + payload.into(), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.fr_accepted_queue), + ) + .await + } + + pub async fn friend_request_received( + &self, + received_request_user: &User, + sent_request_user: &User, + ) -> Result<(), amqprs::error::Error> { + let config = revolt_config::config().await; + let payload = FRReceivedPayload { + from_user: sent_request_user.to_owned(), + user: received_request_user.id.clone(), + }; + let payload = to_string(&payload).unwrap(); + + info!("Sending friend request received event: {}", &payload); + + self.channel + .basic_publish( + BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(), + payload.into(), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.fr_received_queue), + ) + .await + } + + pub async fn generic_message( + &self, + user: &User, + title: String, + body: String, + icon: Option, + ) -> Result<(), amqprs::error::Error> { + let config = revolt_config::config().await; + let payload = GenericPayload { + title, + body, + icon, + user: user.to_owned(), + }; + let payload = to_string(&payload).unwrap(); + + self.channel + .basic_publish( + BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(), + payload.into(), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.generic_queue), + ) + .await + } + + pub async fn message_sent( + &self, + recipients: Vec, + payload: PushNotification, + ) -> Result<(), amqprs::error::Error> { + if recipients.is_empty() { + return Ok(()); + } + + let config: revolt_config::Settings = revolt_config::config().await; + + let online_ids = filter_online(&recipients).await; + let recipients = (&recipients.into_iter().collect::>() - &online_ids) + .into_iter() + .collect::>(); + + let payload = MessageSentPayload { + notification: payload, + users: recipients, + }; + let payload = to_string(&payload).unwrap(); + + self.channel + .basic_publish( + BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(), + payload.into(), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue), + ) + .await + } +} diff --git a/crates/core/database/src/amqp/mod.rs b/crates/core/database/src/amqp/mod.rs new file mode 100644 index 000000000..3c5bbbc73 --- /dev/null +++ b/crates/core/database/src/amqp/mod.rs @@ -0,0 +1 @@ +pub mod amqp; diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs index de1216e7f..acfdb2806 100644 --- a/crates/core/database/src/events/rabbit.rs +++ b/crates/core/database/src/events/rabbit.rs @@ -3,22 +3,42 @@ use std::collections::HashMap; use revolt_models::v0::PushNotification; use serde::{Deserialize, Serialize}; +use crate::User; + #[derive(Serialize, Deserialize)] -pub struct MessageSentNotification { +pub struct MessageSentPayload { pub notification: PushNotification, pub users: Vec, } -#[derive(Serialize, Deserialize)] -pub struct BasicPayload { +#[derive(Serialize, Deserialize, Clone)] +pub struct FRAcceptedPayload { + pub accepted_user: User, + pub user: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct FRReceivedPayload { + pub from_user: User, + pub user: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct GenericPayload { pub title: String, pub body: String, + pub icon: Option, + pub user: User, } #[derive(Serialize, Deserialize)] #[serde(tag = "type", content = "data")] +#[allow(clippy::large_enum_variant)] pub enum PayloadKind { MessageNotification(PushNotification), + FRAccepted(FRAcceptedPayload), + FRReceived(FRReceivedPayload), + Generic(GenericPayload), } #[derive(Serialize, Deserialize)] diff --git a/crates/core/database/src/lib.rs b/crates/core/database/src/lib.rs index dd911f0d0..4df971c17 100644 --- a/crates/core/database/src/lib.rs +++ b/crates/core/database/src/lib.rs @@ -83,6 +83,9 @@ pub use models::*; pub mod events; pub mod tasks; +mod amqp; +pub use amqp::amqp::AMQP; + /// Utility function to check if a boolean value is false pub fn if_false(t: &bool) -> bool { !t @@ -91,4 +94,4 @@ pub fn if_false(t: &bool) -> bool { /// Utility function to check if an option doesnt contain true pub fn if_option_false(t: &Option) -> bool { t != &Some(true) -} \ No newline at end of file +} diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 2639504a8..cdfdd34f5 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, str::FromStr, time::Duration}; -use crate::{events::client::EventV1, Database, File, RatelimitEvent}; +use crate::{events::client::EventV1, Database, File, RatelimitEvent, AMQP}; use once_cell::sync::Lazy; use rand::seq::SliceRandom; @@ -485,7 +485,12 @@ impl User { } /// Add another user as a friend - pub async fn add_friend(&mut self, db: &Database, target: &mut User) -> Result<()> { + pub async fn add_friend( + &mut self, + db: &Database, + amqp: &AMQP, + target: &mut User, + ) -> Result<()> { match self.relationship_with(&target.id) { RelationshipStatus::User => Err(create_error!(NoEffect)), RelationshipStatus::Friend => Err(create_error!(AlreadyFriends)), @@ -494,6 +499,8 @@ impl User { RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)), RelationshipStatus::Incoming => { // Accept incoming friend request + _ = amqp.friend_request_accepted(self, target).await; + self.apply_relationship( db, target, @@ -522,6 +529,8 @@ impl User { })); } + _ = amqp.friend_request_received(target, self).await; + // Send the friend request self.apply_relationship( db, diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index bcd8f6f7e..9cbb0fd2d 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -22,7 +22,7 @@ use web_push::{ }; use super::apple_notifications; -use crate::events::rabbit::{self, MessageSentNotification}; +use crate::events::rabbit::{self, MessageSentPayload}; /// Task information #[derive(Debug)] @@ -95,7 +95,7 @@ pub async fn worker(db: Database) { loop { let task = Q.pop().await; - let payload = MessageSentNotification { + let payload = MessageSentPayload { notification: task.payload, users: task.recipients, }; diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index 066091249..3fd7416fd 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -65,6 +65,9 @@ rocket_prometheus = "0.10.0-rc.3" schemars = "0.8.8" revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] } +# rabbit +amqprs = { version = "1.7.0" } + # core authifier = "1.0.8" revolt-config = { path = "../core/config" } diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 844bcc243..2e8b7e2ea 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -10,13 +10,14 @@ pub mod util; use revolt_config::config; use revolt_database::events::client::EventV1; -use revolt_database::{Database, MongoDb}; +use revolt_database::{Database, MongoDb, AMQP}; use rocket::{Build, Rocket}; use rocket_cors::{AllowedOrigins, CorsOptions}; use rocket_prometheus::PrometheusMetrics; use std::net::Ipv4Addr; use std::str::FromStr; +use amqprs::connection::{Connection, OpenConnectionArguments}; use async_std::channel::unbounded; use authifier::config::{ Captcha, Config as AuthifierConfig, EmailVerificationConfig, ResolveIp, SMTPSettings, Shield, @@ -96,6 +97,19 @@ pub async fn web() -> Rocket { ) .into(); + // Configure Rabbit + let connection = Connection::open(&OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + )) + .await + .unwrap(); + let channel = connection.open_channel(None).await.unwrap(); + + let amqp = AMQP::new(connection, channel); + // Configure Rocket let rocket = rocket::build(); let prometheus = PrometheusMetrics::new(); @@ -108,6 +122,7 @@ pub async fn web() -> Rocket { .mount("/swagger/", swagger) .manage(authifier) .manage(db) + .manage(amqp) .manage(cors.clone()) .attach(util::ratelimiter::RatelimitFairing) .attach(cors) diff --git a/crates/delta/src/routes/users/add_friend.rs b/crates/delta/src/routes/users/add_friend.rs index f544c766a..b7728d0ea 100644 --- a/crates/delta/src/routes/users/add_friend.rs +++ b/crates/delta/src/routes/users/add_friend.rs @@ -1,5 +1,5 @@ use revolt_database::util::reference::Reference; -use revolt_database::{Database, User}; +use revolt_database::{Database, User, AMQP}; use revolt_models::v0; use revolt_result::{create_error, Result}; use rocket::serde::json::Json; @@ -12,6 +12,7 @@ use rocket::State; #[put("//friend")] pub async fn add( db: &State, + amqp: &State, mut user: User, target: Reference, ) -> Result> { @@ -21,6 +22,6 @@ pub async fn add( return Err(create_error!(IsBot)); } - user.add_friend(db, &mut target).await?; + user.add_friend(db, amqp, &mut target).await?; Ok(Json(target.into(db, &user).await)) } diff --git a/crates/delta/src/routes/users/send_friend_request.rs b/crates/delta/src/routes/users/send_friend_request.rs index 552219f25..a87334af2 100644 --- a/crates/delta/src/routes/users/send_friend_request.rs +++ b/crates/delta/src/routes/users/send_friend_request.rs @@ -1,5 +1,5 @@ -use revolt_database::util::reference::Reference; -use revolt_database::{Database, User}; +// use revolt_database::util::reference::Reference; +use revolt_database::{Database, User, AMQP}; use revolt_models::v0; use revolt_result::{create_error, Result}; use rocket::serde::json::Json; @@ -12,6 +12,7 @@ use rocket::State; #[post("/friend", data = "")] pub async fn send_friend_request( db: &State, + amqp: &State, mut user: User, data: Json, ) -> Result> { @@ -22,7 +23,7 @@ pub async fn send_friend_request( return Err(create_error!(IsBot)); } - user.add_friend(db, &mut target).await?; + user.add_friend(db, amqp, &mut target).await?; Ok(Json(target.into(db, &user).await)) } else { Err(create_error!(InvalidProperty)) diff --git a/crates/pushd/Cargo.toml b/crates/pushd/Cargo.toml index ac87b8274..10d42989a 100644 --- a/crates/pushd/Cargo.toml +++ b/crates/pushd/Cargo.toml @@ -10,9 +10,7 @@ revolt-models = { version = "0.7.15", path = "../core/models", features = [ "validator", ] } -tracing = "0.1" -tracing-subscriber = "0.1" -amqprs = { version = "1.7.0", features = ["traces"] } +amqprs = { version = "1.7.0" } fcm = "0.9.2" web-push = "0.10.0" isahc = { optional = true, version = "1.7", features = ["json"] } @@ -23,6 +21,8 @@ ulid = "1.0.0" authifier = "1.0.8" +log = "0.4.11" + #serialization serde_json = "1" revolt_optional_struct = "0.2.0" diff --git a/crates/pushd/src/consumers/inbound/fr_accepted.rs b/crates/pushd/src/consumers/inbound/fr_accepted.rs new file mode 100644 index 000000000..2e0a7af08 --- /dev/null +++ b/crates/pushd/src/consumers/inbound/fr_accepted.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; + +use crate::consumers::inbound::internal::*; +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + connection::Connection, + consumer::AsyncConsumer, + BasicProperties, Deliver, +}; +use async_trait::async_trait; +use log::info; +use revolt_database::{events::rabbit::*, Database}; + +pub struct FRAcceptedConsumer { + #[allow(dead_code)] + db: Database, + authifier_db: authifier::Database, + conn: Option, + channel: Option, +} + +impl Channeled for FRAcceptedConsumer { + fn get_connection(&self) -> Option<&Connection> { + if self.conn.is_none() { + None + } else { + Some(self.conn.as_ref().unwrap()) + } + } + + fn get_channel(&self) -> Option<&Channel> { + if self.channel.is_none() { + None + } else { + Some(self.channel.as_ref().unwrap()) + } + } + + fn set_connection(&mut self, conn: Connection) { + self.conn = Some(conn); + } + + fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } +} + +impl FRAcceptedConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> FRAcceptedConsumer { + FRAcceptedConsumer { + db, + authifier_db, + conn: None, + channel: None, + } + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for FRAcceptedConsumer { + /// This consumer handles delegating messages into their respective platform queues. + async fn consume( + &mut self, + channel: &Channel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: FRAcceptedPayload = serde_json::from_str(content.as_str()).unwrap(); + + println!("Received FR accept event"); + + if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user).await { + let config = revolt_config::config().await; + for session in sessions { + if let Some(sub) = session.subscription { + let mut sendable = PayloadToService { + notification: PayloadKind::FRAccepted(payload.clone()), + token: sub.auth, + user_id: session.user_id, + session_id: session.id, + extras: HashMap::new(), + }; + + let args: BasicPublishArguments; + + if sub.endpoint == "apn" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.apn.queue.as_str(), + ) + .finish(); + } else if sub.endpoint == "fcm" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.fcm.queue.as_str(), + ) + .finish(); + } else { + // web push (vapid) + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.vapid.queue.as_str(), + ) + .finish(); + sendable.extras.insert("p265dh".to_string(), sub.p256dh); + sendable + .extras + .insert("endpoint".to_string(), sub.endpoint.clone()); + } + + let payload = serde_json::to_string(&sendable).unwrap(); + + publish_message(self, payload.into(), args).await; + } + } + } + } +} diff --git a/crates/pushd/src/consumers/inbound/fr_received.rs b/crates/pushd/src/consumers/inbound/fr_received.rs new file mode 100644 index 000000000..2aac550ca --- /dev/null +++ b/crates/pushd/src/consumers/inbound/fr_received.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; + +use crate::consumers::inbound::internal::*; +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + connection::Connection, + consumer::AsyncConsumer, + BasicProperties, Deliver, +}; +use async_trait::async_trait; +use log::info; +use revolt_database::{events::rabbit::*, Database}; + +pub struct FRReceivedConsumer { + #[allow(dead_code)] + db: Database, + authifier_db: authifier::Database, + conn: Option, + channel: Option, +} + +impl Channeled for FRReceivedConsumer { + fn get_connection(&self) -> Option<&Connection> { + if self.conn.is_none() { + None + } else { + Some(self.conn.as_ref().unwrap()) + } + } + + fn get_channel(&self) -> Option<&Channel> { + if self.channel.is_none() { + None + } else { + Some(self.channel.as_ref().unwrap()) + } + } + + fn set_connection(&mut self, conn: Connection) { + self.conn = Some(conn); + } + + fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } +} + +impl FRReceivedConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> FRReceivedConsumer { + FRReceivedConsumer { + db, + authifier_db, + conn: None, + channel: None, + } + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for FRReceivedConsumer { + /// This consumer handles delegating messages into their respective platform queues. + async fn consume( + &mut self, + channel: &Channel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: FRReceivedPayload = serde_json::from_str(content.as_str()).unwrap(); + + println!("Received FR received event"); + + if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user).await { + let config = revolt_config::config().await; + for session in sessions { + if let Some(sub) = session.subscription { + let mut sendable = PayloadToService { + notification: PayloadKind::FRReceived(payload.clone()), + token: sub.auth, + user_id: session.user_id, + session_id: session.id, + extras: HashMap::new(), + }; + + let args: BasicPublishArguments; + + if sub.endpoint == "apn" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.apn.queue.as_str(), + ) + .finish(); + } else if sub.endpoint == "fcm" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.fcm.queue.as_str(), + ) + .finish(); + } else { + // web push (vapid) + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.vapid.queue.as_str(), + ) + .finish(); + sendable.extras.insert("p265dh".to_string(), sub.p256dh); + sendable + .extras + .insert("endpoint".to_string(), sub.endpoint.clone()); + } + + let payload = serde_json::to_string(&sendable).unwrap(); + + publish_message(self, payload.into(), args).await; + } + } + } + } +} diff --git a/crates/pushd/src/consumers/origin.rs b/crates/pushd/src/consumers/inbound/generic.rs similarity index 60% rename from crates/pushd/src/consumers/origin.rs rename to crates/pushd/src/consumers/inbound/generic.rs index 6802be1d7..58070f664 100644 --- a/crates/pushd/src/consumers/origin.rs +++ b/crates/pushd/src/consumers/inbound/generic.rs @@ -1,16 +1,17 @@ use std::collections::HashMap; +use crate::consumers::inbound::internal::*; use amqprs::{ channel::{BasicPublishArguments, Channel}, - connection::{Connection, OpenConnectionArguments}, + connection::Connection, consumer::AsyncConsumer, BasicProperties, Deliver, }; use async_trait::async_trait; +use log::debug; use revolt_database::{events::rabbit::*, Database}; -use tracing::debug; -pub struct OriginMessageConsumer { +pub struct GenericConsumer { #[allow(dead_code)] db: Database, authifier_db: authifier::Database, @@ -18,77 +19,46 @@ pub struct OriginMessageConsumer { channel: Option, } -impl OriginMessageConsumer { - pub fn new(db: Database, authifier_db: authifier::Database) -> OriginMessageConsumer { - OriginMessageConsumer { - db, - authifier_db, - conn: None, - channel: None, +impl Channeled for GenericConsumer { + fn get_connection(&self) -> Option<&Connection> { + if self.conn.is_none() { + None + } else { + Some(self.conn.as_ref().unwrap()) } } - async fn make_channel(&mut self) { - let config = revolt_config::config().await; - - let args = OpenConnectionArguments::new( - &config.rabbit.host, - config.rabbit.port, - &config.rabbit.username, - &config.rabbit.password, - ); - self.conn = Some(amqprs::connection::Connection::open(&args).await.unwrap()); - - let _raw_channel = self - .conn - .as_ref() - .unwrap() - .open_channel(None) - .await - .unwrap(); + fn get_channel(&self) -> Option<&Channel> { + if self.channel.is_none() { + None + } else { + Some(self.channel.as_ref().unwrap()) + } + } - self.channel = Some(_raw_channel); + fn set_connection(&mut self, conn: Connection) { + self.conn = Some(conn); } - async fn publish_message( - &mut self, - payload: Vec, - args: BasicPublishArguments, - attempt: u8, - ) { - let routing_key = &args.routing_key.clone(); - if attempt > 3 { - panic!( - "Failed 3 attempts to send a message to queue {}", - routing_key - ); - } - if self.channel.is_none() { - self.make_channel().await; - } + fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } +} - if let Some(chnl) = &self.channel { - //if let Err(err) = - chnl.basic_publish(BasicProperties::default(), payload.clone(), args.clone()) - .await - .unwrap(); - // { - // match err { - // Error::InternalChannelError(_) => { - // self.make_channel().await; - // self.publish_message(payload, args, attempt + 1).await; - // } - // _ => {} - // } - // } - debug!("Sent message to queue for target {}", routing_key); +impl GenericConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> GenericConsumer { + GenericConsumer { + db, + authifier_db, + conn: None, + channel: None, } } } #[allow(unused_variables)] #[async_trait] -impl AsyncConsumer for OriginMessageConsumer { +impl AsyncConsumer for GenericConsumer { /// This consumer handles delegating messages into their respective platform queues. async fn consume( &mut self, @@ -98,7 +68,7 @@ impl AsyncConsumer for OriginMessageConsumer { content: Vec, ) { let content = String::from_utf8(content).unwrap(); - let payload: MessageSentNotification = serde_json::from_str(content.as_str()).unwrap(); + let payload: MessageSentPayload = serde_json::from_str(content.as_str()).unwrap(); debug!("Received message event on origin"); @@ -149,7 +119,7 @@ impl AsyncConsumer for OriginMessageConsumer { let payload = serde_json::to_string(&sendable).unwrap(); - self.publish_message(payload.into(), args, 1).await; + publish_message(self, payload.into(), args).await; } } } diff --git a/crates/pushd/src/consumers/inbound/internal.rs b/crates/pushd/src/consumers/inbound/internal.rs new file mode 100644 index 000000000..90430932c --- /dev/null +++ b/crates/pushd/src/consumers/inbound/internal.rs @@ -0,0 +1,63 @@ +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + connection::{Connection, OpenConnectionArguments}, + BasicProperties, +}; +use log::{debug, warn}; + +pub(crate) trait Channeled { + #[allow(unused)] + fn get_connection(&self) -> Option<&Connection>; + fn get_channel(&self) -> Option<&Channel>; + fn set_connection(&mut self, conn: Connection); + fn set_channel(&mut self, channel: Channel); +} + +pub(crate) async fn make_channel(consumer: &mut T) { + let config = revolt_config::config().await; + + let args = OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + ); + let conn = amqprs::connection::Connection::open(&args).await.unwrap(); + + let channel = conn.open_channel(None).await.unwrap(); + + consumer.set_connection(conn); + consumer.set_channel(channel); +} + +pub(crate) async fn publish_message( + consumer: &mut T, + payload: Vec, + args: BasicPublishArguments, +) { + let routing_key = &args.routing_key.clone(); + let mut channel = consumer.get_channel(); + if channel.is_none() { + make_channel(consumer).await; + channel = consumer.get_channel(); + } + + if let Some(chnl) = channel { + //if let Err(err) = + chnl.basic_publish(BasicProperties::default(), payload.clone(), args.clone()) + .await + .unwrap(); + // { + // match err { + // Error::InternalChannelError(_) => { + // self.make_channel().await; + // self.publish_message(payload, args, attempt + 1).await; + // } + // _ => {} + // } + // } + debug!("Sent message to queue for target {}", routing_key); + } else { + warn!("Failed to unwrap channel (including attempt to make a channel)!") + } +} diff --git a/crates/pushd/src/consumers/inbound/message.rs b/crates/pushd/src/consumers/inbound/message.rs new file mode 100644 index 000000000..99e61cbd5 --- /dev/null +++ b/crates/pushd/src/consumers/inbound/message.rs @@ -0,0 +1,127 @@ +use std::collections::HashMap; + +use crate::consumers::inbound::internal::*; +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + connection::Connection, + consumer::AsyncConsumer, + BasicProperties, Deliver, +}; +use async_trait::async_trait; +use log::debug; +use revolt_database::{events::rabbit::*, Database}; + +pub struct MessageConsumer { + #[allow(dead_code)] + db: Database, + authifier_db: authifier::Database, + conn: Option, + channel: Option, +} + +impl Channeled for MessageConsumer { + fn get_connection(&self) -> Option<&Connection> { + if self.conn.is_none() { + None + } else { + Some(self.conn.as_ref().unwrap()) + } + } + + fn get_channel(&self) -> Option<&Channel> { + if self.channel.is_none() { + None + } else { + Some(self.channel.as_ref().unwrap()) + } + } + + fn set_connection(&mut self, conn: Connection) { + self.conn = Some(conn); + } + + fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } +} + +impl MessageConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> MessageConsumer { + MessageConsumer { + db, + authifier_db, + conn: None, + channel: None, + } + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for MessageConsumer { + /// This consumer handles delegating messages into their respective platform queues. + async fn consume( + &mut self, + channel: &Channel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: MessageSentPayload = serde_json::from_str(content.as_str()).unwrap(); + + debug!("Received message event on origin"); + + if let Ok(sessions) = self + .authifier_db + .find_sessions_with_subscription(&payload.users) + .await + { + let config = revolt_config::config().await; + for session in sessions { + if let Some(sub) = session.subscription { + let mut sendable = PayloadToService { + notification: PayloadKind::MessageNotification( + payload.notification.clone(), + ), + token: sub.auth, + user_id: session.user_id, + session_id: session.id, + extras: HashMap::new(), + }; + + let args: BasicPublishArguments; + + if sub.endpoint == "apn" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.apn.queue.as_str(), + ) + .finish(); + } else if sub.endpoint == "fcm" { + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.fcm.queue.as_str(), + ) + .finish(); + } else { + // web push (vapid) + args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.vapid.queue.as_str(), + ) + .finish(); + sendable.extras.insert("p265dh".to_string(), sub.p256dh); + sendable + .extras + .insert("endpoint".to_string(), sub.endpoint.clone()); + } + + let payload = serde_json::to_string(&sendable).unwrap(); + + publish_message(self, payload.into(), args).await; + } + } + } + } +} diff --git a/crates/pushd/src/consumers/inbound/mod.rs b/crates/pushd/src/consumers/inbound/mod.rs new file mode 100644 index 000000000..3ca8f184a --- /dev/null +++ b/crates/pushd/src/consumers/inbound/mod.rs @@ -0,0 +1,5 @@ +pub mod fr_accepted; +pub mod fr_received; +pub mod generic; +mod internal; +pub mod message; diff --git a/crates/pushd/src/consumers/mod.rs b/crates/pushd/src/consumers/mod.rs index 3b9db3880..5756441b7 100644 --- a/crates/pushd/src/consumers/mod.rs +++ b/crates/pushd/src/consumers/mod.rs @@ -1,2 +1,2 @@ -pub mod origin; +pub mod inbound; pub mod outbound; diff --git a/crates/pushd/src/consumers/outbound/apn.rs b/crates/pushd/src/consumers/outbound/apn.rs index 58449fac0..111a48e2a 100644 --- a/crates/pushd/src/consumers/outbound/apn.rs +++ b/crates/pushd/src/consumers/outbound/apn.rs @@ -1,4 +1,4 @@ -use std::io::Cursor; +use std::{borrow::Cow, collections::BTreeMap, io::Cursor}; use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; use async_trait::async_trait; @@ -9,7 +9,7 @@ use base64::{ use revolt_a2::{ request::{ notification::{DefaultAlert, NotificationOptions}, - payload::{APSAlert, APSSound, PayloadLike, APS}, + payload::{APSAlert, APSSound, Payload, PayloadLike, APS}, }, Client, ClientConfig, Endpoint, Error, ErrorBody, ErrorReason, Priority, PushType, Response, }; @@ -147,6 +147,119 @@ impl AsyncConsumer for ApnsOutboundConsumer { let resp: Result; match payload.notification { + PayloadKind::FRReceived(alert) => { + let loc_args = vec![Cow::from( + alert + .from_user + .display_name + .or(Some(format!( + "{}#{}", + alert.from_user.username, alert.from_user.discriminator + ))) + .clone() + .unwrap(), + )]; + + let apn_payload = Payload { + aps: APS { + alert: Some(APSAlert::Default(DefaultAlert { + title: None, + subtitle: None, + body: None, + title_loc_key: None, + title_loc_args: None, + action_loc_key: None, + loc_key: Some("push.fr.received"), + loc_args: Some(loc_args), + launch_image: None, + })), + badge: self.get_badge_count(&payload.user_id).await, + sound: Some(APSSound::Sound("default")), + thread_id: None, + content_available: None, + category: None, + mutable_content: Some(1), + url_args: None, + }, + device_token: &payload.token, + options: payload_options.clone(), + data: BTreeMap::new(), + }; + + resp = self.client.send(apn_payload).await; + } + + PayloadKind::FRAccepted(alert) => { + let loc_args = vec![Cow::from( + alert + .accepted_user + .display_name + .or(Some(format!( + "{}#{}", + alert.accepted_user.username, alert.accepted_user.discriminator + ))) + .clone() + .unwrap(), + )]; + + let apn_payload = Payload { + aps: APS { + alert: Some(APSAlert::Default(DefaultAlert { + title: None, + subtitle: None, + body: None, + title_loc_key: None, + title_loc_args: None, + action_loc_key: None, + loc_key: Some("push.fr.accepted"), + loc_args: Some(loc_args), + launch_image: None, + })), + badge: self.get_badge_count(&payload.user_id).await, + sound: Some(APSSound::Sound("default")), + thread_id: None, + content_available: None, + category: None, + mutable_content: Some(1), + url_args: None, + }, + device_token: &payload.token, + options: payload_options.clone(), + data: BTreeMap::new(), + }; + + resp = self.client.send(apn_payload).await; + } + PayloadKind::Generic(alert) => { + let apn_payload = Payload { + aps: APS { + alert: Some(APSAlert::Default(DefaultAlert { + title: Some(&alert.title), + subtitle: None, + body: Some(&alert.body), + title_loc_key: None, + title_loc_args: None, + action_loc_key: None, + loc_key: None, + loc_args: None, + launch_image: None, + })), + badge: self.get_badge_count(&payload.user_id).await, + sound: Some(APSSound::Sound("default")), + thread_id: None, + content_available: None, + category: None, + mutable_content: Some(1), + url_args: None, + }, + device_token: &payload.token, + options: payload_options.clone(), + data: BTreeMap::new(), + }; + + resp = self.client.send(apn_payload).await; + } + PayloadKind::MessageNotification(alert) => { let title = self.format_title(&alert); let apn_payload = MessagePayload { diff --git a/crates/pushd/src/consumers/outbound/fcm.rs b/crates/pushd/src/consumers/outbound/fcm.rs index 9f9eaa7f5..562bad80a 100644 --- a/crates/pushd/src/consumers/outbound/fcm.rs +++ b/crates/pushd/src/consumers/outbound/fcm.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; use async_trait::async_trait; @@ -58,9 +60,72 @@ impl AsyncConsumer for FcmOutboundConsumer { let payload: PayloadToService = serde_json::from_str(content.as_str()).unwrap(); let config = revolt_config::config().await; + + #[allow(clippy::needless_late_init)] let resp: Result; match payload.notification { + PayloadKind::FRReceived(alert) => { + let name = alert + .from_user + .display_name + .or(Some(format!( + "{}#{}", + alert.from_user.username, alert.from_user.discriminator + ))) + .clone() + .unwrap(); + + let mut data = HashMap::new(); + data.insert("type", "push.fr.receive"); + data.insert("id", &alert.from_user.id); + data.insert("username", &name); + + let mut message_builder = + MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); + _ = message_builder.data(&data); + + resp = self.client.send(message_builder.finalize()).await; + } + + PayloadKind::FRAccepted(alert) => { + let name = alert + .accepted_user + .display_name + .or(Some(format!( + "{}#{}", + alert.accepted_user.username, alert.accepted_user.discriminator + ))) + .clone() + .unwrap(); + + let mut data = HashMap::new(); + data.insert("type", "push.fr.accepted"); + data.insert("id", &alert.accepted_user.id); + data.insert("username", &name); + + let mut message_builder = + MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); + _ = message_builder.data(&data); + + resp = self.client.send(message_builder.finalize()).await; + } + PayloadKind::Generic(alert) => { + let mut notification = fcm::NotificationBuilder::new(); + notification.title(alert.title.as_str()); + if alert.icon.is_some() { + notification.icon(alert.icon.as_ref().unwrap()); + } + notification.body(&alert.body); + let notification = notification.finalize(); + + let mut message_builder = + MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); + message_builder.notification(notification); + + resp = self.client.send(message_builder.finalize()).await; + } + PayloadKind::MessageNotification(alert) => { let title = self.format_title(&alert); diff --git a/crates/pushd/src/consumers/outbound/vapid.rs b/crates/pushd/src/consumers/outbound/vapid.rs index 91a743a95..e0e0c2a8e 100644 --- a/crates/pushd/src/consumers/outbound/vapid.rs +++ b/crates/pushd/src/consumers/outbound/vapid.rs @@ -6,7 +6,7 @@ use base64::{ Engine as _, }; use revolt_database::{events::rabbit::*, Database}; -use revolt_models::v0::{Channel, PushNotification}; +// use revolt_models::v0::{Channel, PushNotification}; use web_push::{ ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, WebPushError, WebPushMessageBuilder, @@ -18,24 +18,24 @@ pub struct VapidOutboundConsumer { pkey: Vec, } -impl VapidOutboundConsumer { - fn format_title(&self, notification: &PushNotification) -> String { - // ideally this changes depending on context - // in a server, it would look like "Sendername, #channelname in servername" - // in a group, it would look like "Sendername in groupname" - // in a dm it should just be "Sendername". - // not sure how feasible all those are given the PushNotification object as it currently stands. - - match ¬ification.channel { - Channel::DirectMessage { .. } => notification.author.clone(), - Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), - Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { - format!("{} in #{}", notification.author, name) - } - _ => "Unknown".to_string(), - } - } -} +// impl VapidOutboundConsumer { +// fn format_title(&self, notification: &PushNotification) -> String { +// // ideally this changes depending on context +// // in a server, it would look like "Sendername, #channelname in servername" +// // in a group, it would look like "Sendername in groupname" +// // in a dm it should just be "Sendername". +// // not sure how feasible all those are given the PushNotification object as it currently stands. + +// match ¬ification.channel { +// Channel::DirectMessage { .. } => notification.author.clone(), +// Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), +// Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { +// format!("{} in #{}", notification.author, name) +// } +// _ => "Unknown".to_string(), +// } +// } +// } impl VapidOutboundConsumer { pub async fn new(db: Database) -> Result { @@ -80,50 +80,57 @@ impl AsyncConsumer for VapidOutboundConsumer { }, }; + #[allow(clippy::needless_late_init)] + let payload_body: String; + match payload.notification { + PayloadKind::FRReceived(alert) => { + payload_body = serde_json::to_string(&alert).unwrap(); + } + PayloadKind::FRAccepted(alert) => { + payload_body = serde_json::to_string(&alert).unwrap(); + } + PayloadKind::Generic(alert) => { + payload_body = serde_json::to_string(&alert).unwrap(); + } PayloadKind::MessageNotification(alert) => { - let title = self.format_title(&alert); - match VapidSignatureBuilder::from_pem( - std::io::Cursor::new(&self.pkey), - &subscription, - ) { - Ok(sig_builder) => match sig_builder.build() { - Ok(signature) => { - let mut builder = WebPushMessageBuilder::new(&subscription); - builder.set_vapid_signature(signature); - - let alert_json = serde_json::to_string(&alert).unwrap(); - builder.set_payload(ContentEncoding::AesGcm, alert_json.as_bytes()); - - match builder.build() { - Ok(msg) => { - if let Err(err) = self.client.send(msg).await { - if err == WebPushError::Unauthorized { - if let Err(err) = self - .db - .remove_push_subscription_by_session_id( - &payload.session_id, - ) - .await - { - revolt_config::capture_error(&err); - } - } + payload_body = serde_json::to_string(&alert).unwrap(); + } + } + + match VapidSignatureBuilder::from_pem(std::io::Cursor::new(&self.pkey), &subscription) { + Ok(sig_builder) => match sig_builder.build() { + Ok(signature) => { + let mut builder = WebPushMessageBuilder::new(&subscription); + builder.set_vapid_signature(signature); + + builder.set_payload(ContentEncoding::AesGcm, payload_body.as_bytes()); + + match builder.build() { + Ok(msg) => { + if let Err(err) = self.client.send(msg).await { + if err == WebPushError::Unauthorized { + if let Err(err) = self + .db + .remove_push_subscription_by_session_id(&payload.session_id) + .await + { + revolt_config::capture_error(&err); } } - Err(err) => { - revolt_config::capture_error(&err); - } } } Err(err) => { revolt_config::capture_error(&err); } - }, - Err(err) => { - revolt_config::capture_error(&err); } } + Err(err) => { + revolt_config::capture_error(&err); + } + }, + Err(err) => { + revolt_config::capture_error(&err); } } } diff --git a/crates/pushd/src/main.rs b/crates/pushd/src/main.rs index 446a19fd8..72f094a6a 100644 --- a/crates/pushd/src/main.rs +++ b/crates/pushd/src/main.rs @@ -8,13 +8,17 @@ use tokio::sync::Notify; mod consumers; use consumers::{ - origin::OriginMessageConsumer, outbound::apn::ApnsOutboundConsumer, - outbound::fcm::FcmOutboundConsumer, outbound::vapid::VapidOutboundConsumer, + inbound::{ + fr_accepted::FRAcceptedConsumer, fr_received::FRReceivedConsumer, generic::GenericConsumer, + message::MessageConsumer, + }, + outbound::{apn::ApnsOutboundConsumer, fcm::FcmOutboundConsumer, vapid::VapidOutboundConsumer}, }; -use tracing::info; +use log::info; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { + //log::set_max_level(log::LevelFilter::Trace); let config = config().await; // Setup database @@ -33,11 +37,42 @@ async fn main() { let mut connections: Vec<(Channel, Connection)> = Vec::new(); + // inbound: generic connections.push( make_queue_and_consume( &config, &config.pushd.message_queue, - OriginMessageConsumer::new(db.clone(), authifier.clone()), + GenericConsumer::new(db.clone(), authifier.clone()), + ) + .await, + ); + + // inbound: messages + connections.push( + make_queue_and_consume( + &config, + &config.pushd.message_queue, + MessageConsumer::new(db.clone(), authifier.clone()), + ) + .await, + ); + + // inbound: FR received + connections.push( + make_queue_and_consume( + &config, + &config.pushd.fr_received_queue, + FRReceivedConsumer::new(db.clone(), authifier.clone()), + ) + .await, + ); + + // inbound: FR accepted + connections.push( + make_queue_and_consume( + &config, + &config.pushd.fr_accepted_queue, + FRAcceptedConsumer::new(db.clone(), authifier.clone()), ) .await, ); From 07b31a97731644c5b1c988ad7dffffe34c1b1332 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 24 Aug 2024 23:20:37 -0700 Subject: [PATCH 05/44] make vapid fr stuff not suck --- .../src/consumers/inbound/fr_accepted.rs | 4 +-- .../src/consumers/inbound/fr_received.rs | 4 +-- crates/pushd/src/consumers/outbound/vapid.rs | 32 +++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/pushd/src/consumers/inbound/fr_accepted.rs b/crates/pushd/src/consumers/inbound/fr_accepted.rs index 2e0a7af08..ff084a83a 100644 --- a/crates/pushd/src/consumers/inbound/fr_accepted.rs +++ b/crates/pushd/src/consumers/inbound/fr_accepted.rs @@ -8,7 +8,7 @@ use amqprs::{ BasicProperties, Deliver, }; use async_trait::async_trait; -use log::info; +use log::debug; use revolt_database::{events::rabbit::*, Database}; pub struct FRAcceptedConsumer { @@ -70,7 +70,7 @@ impl AsyncConsumer for FRAcceptedConsumer { let content = String::from_utf8(content).unwrap(); let payload: FRAcceptedPayload = serde_json::from_str(content.as_str()).unwrap(); - println!("Received FR accept event"); + debug!("Received FR accept event"); if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user).await { let config = revolt_config::config().await; diff --git a/crates/pushd/src/consumers/inbound/fr_received.rs b/crates/pushd/src/consumers/inbound/fr_received.rs index 2aac550ca..c52dfec1b 100644 --- a/crates/pushd/src/consumers/inbound/fr_received.rs +++ b/crates/pushd/src/consumers/inbound/fr_received.rs @@ -8,7 +8,7 @@ use amqprs::{ BasicProperties, Deliver, }; use async_trait::async_trait; -use log::info; +use log::debug; use revolt_database::{events::rabbit::*, Database}; pub struct FRReceivedConsumer { @@ -70,7 +70,7 @@ impl AsyncConsumer for FRReceivedConsumer { let content = String::from_utf8(content).unwrap(); let payload: FRReceivedPayload = serde_json::from_str(content.as_str()).unwrap(); - println!("Received FR received event"); + debug!("Received FR received event"); if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user).await { let config = revolt_config::config().await; diff --git a/crates/pushd/src/consumers/outbound/vapid.rs b/crates/pushd/src/consumers/outbound/vapid.rs index e0e0c2a8e..4adcc5ffc 100644 --- a/crates/pushd/src/consumers/outbound/vapid.rs +++ b/crates/pushd/src/consumers/outbound/vapid.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; use async_trait::async_trait; @@ -85,10 +87,36 @@ impl AsyncConsumer for VapidOutboundConsumer { match payload.notification { PayloadKind::FRReceived(alert) => { - payload_body = serde_json::to_string(&alert).unwrap(); + let name = alert + .from_user + .display_name + .or(Some(format!( + "{}#{}", + alert.from_user.username, alert.from_user.discriminator + ))) + .clone() + .unwrap(); + + let mut body = HashMap::new(); + body.insert("body", format!("{} sent you a friend request", name)); + + payload_body = serde_json::to_string(&body).unwrap(); } PayloadKind::FRAccepted(alert) => { - payload_body = serde_json::to_string(&alert).unwrap(); + let name = alert + .accepted_user + .display_name + .or(Some(format!( + "{}#{}", + alert.accepted_user.username, alert.accepted_user.discriminator + ))) + .clone() + .unwrap(); + + let mut body = HashMap::new(); + body.insert("body", format!("{} accepted your friend request", name)); + + payload_body = serde_json::to_string(&body).unwrap(); } PayloadKind::Generic(alert) => { payload_body = serde_json::to_string(&alert).unwrap(); From 79d333fa0f09bbbb0e217509e0345a45d60da231 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 3 Sep 2024 22:07:19 -0700 Subject: [PATCH 06/44] swap naming of queue --- crates/pushd/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pushd/src/main.rs b/crates/pushd/src/main.rs index 72f094a6a..9ec95da68 100644 --- a/crates/pushd/src/main.rs +++ b/crates/pushd/src/main.rs @@ -41,7 +41,7 @@ async fn main() { connections.push( make_queue_and_consume( &config, - &config.pushd.message_queue, + &config.pushd.generic_queue, GenericConsumer::new(db.clone(), authifier.clone()), ) .await, From 3b435e8866b92208524dc7698bd43ecb6ff4d67c Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 3 Sep 2024 22:39:30 -0700 Subject: [PATCH 07/44] move pushd into daemons folder --- Cargo.toml | 1 + crates/{ => daemons}/pushd/Cargo.toml | 2 +- crates/{ => daemons}/pushd/src/consumers/base.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/fr_accepted.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/fr_received.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/generic.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/internal.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/message.rs | 0 crates/{ => daemons}/pushd/src/consumers/inbound/mod.rs | 0 crates/{ => daemons}/pushd/src/consumers/mod.rs | 0 crates/{ => daemons}/pushd/src/consumers/outbound/apn.rs | 0 crates/{ => daemons}/pushd/src/consumers/outbound/fcm.rs | 0 crates/{ => daemons}/pushd/src/consumers/outbound/mod.rs | 0 crates/{ => daemons}/pushd/src/consumers/outbound/vapid.rs | 0 crates/{ => daemons}/pushd/src/main.rs | 0 15 files changed, 2 insertions(+), 1 deletion(-) rename crates/{ => daemons}/pushd/Cargo.toml (97%) rename crates/{ => daemons}/pushd/src/consumers/base.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/fr_accepted.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/fr_received.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/generic.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/internal.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/message.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/inbound/mod.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/mod.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/outbound/apn.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/outbound/fcm.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/outbound/mod.rs (100%) rename crates/{ => daemons}/pushd/src/consumers/outbound/vapid.rs (100%) rename crates/{ => daemons}/pushd/src/main.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 40d640857..dee195b96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/core/*", "crates/services/*", "crates/bindings/*", + "crates/daemons/pushd", ] [patch.crates-io] diff --git a/crates/pushd/Cargo.toml b/crates/daemons/pushd/Cargo.toml similarity index 97% rename from crates/pushd/Cargo.toml rename to crates/daemons/pushd/Cargo.toml index 10d42989a..1663a3979 100644 --- a/crates/pushd/Cargo.toml +++ b/crates/daemons/pushd/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pushd" +name = "revolt-pushd" version = "0.1.0" edition = "2021" diff --git a/crates/pushd/src/consumers/base.rs b/crates/daemons/pushd/src/consumers/base.rs similarity index 100% rename from crates/pushd/src/consumers/base.rs rename to crates/daemons/pushd/src/consumers/base.rs diff --git a/crates/pushd/src/consumers/inbound/fr_accepted.rs b/crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/fr_accepted.rs rename to crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs diff --git a/crates/pushd/src/consumers/inbound/fr_received.rs b/crates/daemons/pushd/src/consumers/inbound/fr_received.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/fr_received.rs rename to crates/daemons/pushd/src/consumers/inbound/fr_received.rs diff --git a/crates/pushd/src/consumers/inbound/generic.rs b/crates/daemons/pushd/src/consumers/inbound/generic.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/generic.rs rename to crates/daemons/pushd/src/consumers/inbound/generic.rs diff --git a/crates/pushd/src/consumers/inbound/internal.rs b/crates/daemons/pushd/src/consumers/inbound/internal.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/internal.rs rename to crates/daemons/pushd/src/consumers/inbound/internal.rs diff --git a/crates/pushd/src/consumers/inbound/message.rs b/crates/daemons/pushd/src/consumers/inbound/message.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/message.rs rename to crates/daemons/pushd/src/consumers/inbound/message.rs diff --git a/crates/pushd/src/consumers/inbound/mod.rs b/crates/daemons/pushd/src/consumers/inbound/mod.rs similarity index 100% rename from crates/pushd/src/consumers/inbound/mod.rs rename to crates/daemons/pushd/src/consumers/inbound/mod.rs diff --git a/crates/pushd/src/consumers/mod.rs b/crates/daemons/pushd/src/consumers/mod.rs similarity index 100% rename from crates/pushd/src/consumers/mod.rs rename to crates/daemons/pushd/src/consumers/mod.rs diff --git a/crates/pushd/src/consumers/outbound/apn.rs b/crates/daemons/pushd/src/consumers/outbound/apn.rs similarity index 100% rename from crates/pushd/src/consumers/outbound/apn.rs rename to crates/daemons/pushd/src/consumers/outbound/apn.rs diff --git a/crates/pushd/src/consumers/outbound/fcm.rs b/crates/daemons/pushd/src/consumers/outbound/fcm.rs similarity index 100% rename from crates/pushd/src/consumers/outbound/fcm.rs rename to crates/daemons/pushd/src/consumers/outbound/fcm.rs diff --git a/crates/pushd/src/consumers/outbound/mod.rs b/crates/daemons/pushd/src/consumers/outbound/mod.rs similarity index 100% rename from crates/pushd/src/consumers/outbound/mod.rs rename to crates/daemons/pushd/src/consumers/outbound/mod.rs diff --git a/crates/pushd/src/consumers/outbound/vapid.rs b/crates/daemons/pushd/src/consumers/outbound/vapid.rs similarity index 100% rename from crates/pushd/src/consumers/outbound/vapid.rs rename to crates/daemons/pushd/src/consumers/outbound/vapid.rs diff --git a/crates/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs similarity index 100% rename from crates/pushd/src/main.rs rename to crates/daemons/pushd/src/main.rs From 2c78765eeda80c4bb8373ad80eb217e580f400a1 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 3 Sep 2024 22:45:02 -0700 Subject: [PATCH 08/44] fix cargo file for move into daemons folder --- crates/daemons/pushd/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/daemons/pushd/Cargo.toml b/crates/daemons/pushd/Cargo.toml index 1663a3979..34468ada6 100644 --- a/crates/daemons/pushd/Cargo.toml +++ b/crates/daemons/pushd/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -revolt-config = { version = "0.7.15", path = "../core/config" } -revolt-database = { version = "0.7.15", path = "../core/database" } -revolt-models = { version = "0.7.15", path = "../core/models", features = [ +revolt-config = { version = "0.7.15", path = "../../core/config" } +revolt-database = { version = "0.7.15", path = "../../core/database" } +revolt-models = { version = "0.7.15", path = "../../core/models", features = [ "validator", ] } From 263d16bc557c9c2c75804447ec439c8daaf88383 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 3 Sep 2024 23:17:45 -0700 Subject: [PATCH 09/44] feat: probably working fcm push notifs --- Cargo.lock | 24 +++++ crates/daemons/pushd/Cargo.toml | 2 +- .../pushd/src/consumers/outbound/fcm.rs | 99 ++++++++++++------- 3 files changed, 88 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7dd52618..63e5ef833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5729,6 +5729,30 @@ dependencies = [ "redis-kiss", ] +[[package]] +name = "revolt-pushd" +version = "0.1.0" +dependencies = [ + "amqprs", + "async-trait", + "authifier", + "base64 0.22.1", + "fcm_v1", + "isahc", + "iso8601-timestamp 0.2.17", + "log", + "revolt-config", + "revolt-database", + "revolt-models", + "revolt_a2", + "revolt_optional_struct", + "serde", + "serde_json", + "tokio 1.40.0", + "ulid 1.1.3", + "web-push", +] + [[package]] name = "revolt-result" version = "0.7.16" diff --git a/crates/daemons/pushd/Cargo.toml b/crates/daemons/pushd/Cargo.toml index 34468ada6..f27fef600 100644 --- a/crates/daemons/pushd/Cargo.toml +++ b/crates/daemons/pushd/Cargo.toml @@ -11,7 +11,7 @@ revolt-models = { version = "0.7.15", path = "../../core/models", features = [ ] } amqprs = { version = "1.7.0" } -fcm = "0.9.2" +fcm_v1 = "0.3.0" web-push = "0.10.0" isahc = { optional = true, version = "1.7", features = ["json"] } revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } diff --git a/crates/daemons/pushd/src/consumers/outbound/fcm.rs b/crates/daemons/pushd/src/consumers/outbound/fcm.rs index 562bad80a..38751abdd 100644 --- a/crates/daemons/pushd/src/consumers/outbound/fcm.rs +++ b/crates/daemons/pushd/src/consumers/outbound/fcm.rs @@ -1,9 +1,14 @@ -use std::collections::HashMap; +use std::{any::Any, collections::HashMap, time::Duration}; use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; use async_trait::async_trait; -use fcm::{Client, FcmError, FcmResponse, MessageBuilder}; +use fcm_v1::{ + android::AndroidConfig, + auth::{Authenticator, ServiceAccountKey}, + message::{Message, Notification}, + Client, Error as FcmError, +}; use revolt_database::{events::rabbit::*, Database}; use revolt_models::v0::{Channel, PushNotification}; @@ -41,7 +46,25 @@ impl FcmOutboundConsumer { Ok(FcmOutboundConsumer { db, - client: Client::new(), + client: Client::new( + Authenticator::service_account::<&str>(ServiceAccountKey { + key_type: Some(config.pushd.fcm.key_type), + project_id: Some(config.pushd.fcm.project_id.clone()), + private_key_id: Some(config.pushd.fcm.private_key_id), + private_key: config.pushd.fcm.private_key, + client_email: config.pushd.fcm.client_email, + client_id: Some(config.pushd.fcm.client_id), + auth_uri: Some(config.pushd.fcm.auth_uri), + token_uri: config.pushd.fcm.token_uri, + auth_provider_x509_cert_url: Some(config.pushd.fcm.auth_provider_x509_cert_url), + client_x509_cert_url: Some(config.pushd.fcm.client_x509_cert_url), + }) + .await + .unwrap(), + config.pushd.fcm.project_id, + false, + Duration::from_secs(5), + ), }) } } @@ -81,11 +104,13 @@ impl AsyncConsumer for FcmOutboundConsumer { data.insert("id", &alert.from_user.id); data.insert("username", &name); - let mut message_builder = - MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); - _ = message_builder.data(&data); + let msg = Message { + token: Some(payload.token), + data: Some(data), + ..Default::default() + }; - resp = self.client.send(message_builder.finalize()).await; + resp = self.client.send(&msg).await; } PayloadKind::FRAccepted(alert) => { @@ -104,42 +129,44 @@ impl AsyncConsumer for FcmOutboundConsumer { data.insert("id", &alert.accepted_user.id); data.insert("username", &name); - let mut message_builder = - MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); - _ = message_builder.data(&data); + let msg = Message { + token: Some(payload.token), + data: Some(data), + ..Default::default() + }; - resp = self.client.send(message_builder.finalize()).await; + resp = self.client.send(&msg).await; } PayloadKind::Generic(alert) => { - let mut notification = fcm::NotificationBuilder::new(); - notification.title(alert.title.as_str()); - if alert.icon.is_some() { - notification.icon(alert.icon.as_ref().unwrap()); - } - notification.body(&alert.body); - let notification = notification.finalize(); - - let mut message_builder = - MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); - message_builder.notification(notification); - - resp = self.client.send(message_builder.finalize()).await; + let msg = Message { + token: Some(payload.token), + notification: Some(Notification { + title: Some(alert.title), + body: Some(alert.body), + image: alert.icon, + }), + ..Default::default() + }; + + resp = self.client.send(&msg).await; } PayloadKind::MessageNotification(alert) => { let title = self.format_title(&alert); - let mut notification = fcm::NotificationBuilder::new(); - notification.title(title.as_str()); - notification.icon(&alert.icon); - notification.body(&alert.body); - notification.tag(alert.channel.id()); - // TODO: expand support for fields - let notification = notification.finalize(); - - let mut message_builder = - MessageBuilder::new(&config.pushd.fcm.api_key, &payload.token); - message_builder.notification(notification); + let msg = Message { + token: Some(payload.token), + notification: Some(Notification { + title: Some(title), + body: Some(alert.body), + image: Some(alert.icon), + }), + android: Some(AndroidConfig { + collapse_key: Some(alert.tag), + ..Default::default() + }), + ..Default::default() + }; resp = self.client.send(message_builder.finalize()).await; } @@ -147,7 +174,7 @@ impl AsyncConsumer for FcmOutboundConsumer { if let Err(err) = resp { match err { - FcmError::Unauthorized => { + FcmError::Auth => { if let Err(err) = self .db .remove_push_subscription_by_session_id(&payload.session_id) From 22dedeafbd0ef36680c4cf154e6d6d6bf98f5cd5 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 4 Sep 2024 09:53:33 -0700 Subject: [PATCH 10/44] comment out fcm webpush stuff since the config keys dont exist --- crates/core/database/src/tasks/web_push.rs | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index 2de693e18..cfd83017f 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -66,29 +66,29 @@ pub async fn worker(db: Database, authifier_db: AuthifierDatabase) { let config = config().await; // let web_push_client = IsahcWebPushClient::new().unwrap(); - let fcm_client = if config.api.fcm.key_type.is_empty() { - None - } else { - Some(fcm_v1::Client::new( - Authenticator::service_account::<&str>(ServiceAccountKey { - key_type: Some(config.api.fcm.key_type), - project_id: Some(config.api.fcm.project_id.clone()), - private_key_id: Some(config.api.fcm.private_key_id), - private_key: config.api.fcm.private_key, - client_email: config.api.fcm.client_email, - client_id: Some(config.api.fcm.client_id), - auth_uri: Some(config.api.fcm.auth_uri), - token_uri: config.api.fcm.token_uri, - auth_provider_x509_cert_url: Some(config.api.fcm.auth_provider_x509_cert_url), - client_x509_cert_url: Some(config.api.fcm.client_x509_cert_url), - }) - .await - .unwrap(), - config.api.fcm.project_id, - false, - Duration::from_secs(5), - )) - }; + // let fcm_client = if config.api.fcm.key_type.is_empty() { + // None + // } else { + // Some(fcm_v1::Client::new( + // Authenticator::service_account::<&str>(ServiceAccountKey { + // key_type: Some(config.api.fcm.key_type), + // project_id: Some(config.api.fcm.project_id.clone()), + // private_key_id: Some(config.api.fcm.private_key_id), + // private_key: config.api.fcm.private_key, + // client_email: config.api.fcm.client_email, + // client_id: Some(config.api.fcm.client_id), + // auth_uri: Some(config.api.fcm.auth_uri), + // token_uri: config.api.fcm.token_uri, + // auth_provider_x509_cert_url: Some(config.api.fcm.auth_provider_x509_cert_url), + // client_x509_cert_url: Some(config.api.fcm.client_x509_cert_url), + // }) + // .await + // .unwrap(), + // config.api.fcm.project_id, + // false, + // Duration::from_secs(5), + // )) + // }; // let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD // .decode(config.pushd.vapid.private_key) From 449db24d8effc0c4e725dbf7121bd6a74a952f82 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 4 Sep 2024 16:09:19 -0700 Subject: [PATCH 11/44] fix fcm, name queues according to their prod status and configure routing keys --- crates/core/config/Revolt.toml | 5 ++ crates/core/config/src/lib.rs | 27 +++++++++- .../pushd/src/consumers/outbound/fcm.rs | 33 +++++++------ crates/daemons/pushd/src/main.rs | 49 +++++++++++++++---- 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 69f320dc9..02d9c49a2 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -63,6 +63,11 @@ hcaptcha_sitekey = "" max_concurrent_connections = 50 [pushd] +# this changes the names of the queues to not overlap +# prod/beta if they happen to be on the same exchange/instance. +# Usually they have to be, so that messages sent from one or the other get sent to everyone +production = true + exchange = "revolt.notifications" message_queue = "notifications.origin.message" fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 4d8abec81..c8ea38c2d 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -86,7 +86,6 @@ pub struct PushVapid { #[derive(Deserialize, Debug, Clone)] pub struct PushFcm { pub queue: String, - pub api_key: String, pub key_type: String, pub project_id: String, pub private_key_id: String, @@ -137,6 +136,7 @@ pub struct Api { #[derive(Deserialize, Debug, Clone)] pub struct Pushd { + pub production: bool, pub exchange: String, pub message_queue: String, pub fr_accepted_queue: String, @@ -148,6 +148,31 @@ pub struct Pushd { pub apn: PushApn, } +impl Pushd { + fn get_routing_key(&self, key: String) -> String { + match self.production { + true => key + "-prd", + false => key + "-tst", + } + } + + pub fn get_message_routing_key(&self) -> String { + self.get_routing_key(self.message_queue.clone()) + } + + pub fn get_fr_accepted_routing_key(&self) -> String { + self.get_routing_key(self.fr_accepted_queue.clone()) + } + + pub fn get_fr_received_routing_key(&self) -> String { + self.get_routing_key(self.fr_received_queue.clone()) + } + + pub fn get_generic_routing_key(&self) -> String { + self.get_routing_key(self.generic_queue.clone()) + } +} + #[derive(Deserialize, Debug, Clone)] pub struct FilesLimit { pub min_resolution: [usize; 2], diff --git a/crates/daemons/pushd/src/consumers/outbound/fcm.rs b/crates/daemons/pushd/src/consumers/outbound/fcm.rs index 38751abdd..b05afcbf8 100644 --- a/crates/daemons/pushd/src/consumers/outbound/fcm.rs +++ b/crates/daemons/pushd/src/consumers/outbound/fcm.rs @@ -1,4 +1,4 @@ -use std::{any::Any, collections::HashMap, time::Duration}; +use std::{collections::HashMap, time::Duration}; use amqprs::{channel::Channel as AmqpChannel, consumer::AsyncConsumer, BasicProperties, Deliver}; @@ -11,6 +11,7 @@ use fcm_v1::{ }; use revolt_database::{events::rabbit::*, Database}; use revolt_models::v0::{Channel, PushNotification}; +use serde_json::Value; pub struct FcmOutboundConsumer { db: Database, @@ -40,10 +41,6 @@ impl FcmOutboundConsumer { pub async fn new(db: Database) -> Result { let config = revolt_config::config().await; - if config.pushd.fcm.api_key.is_empty() { - return Err("No FCM key present"); - } - Ok(FcmOutboundConsumer { db, client: Client::new( @@ -85,7 +82,7 @@ impl AsyncConsumer for FcmOutboundConsumer { let config = revolt_config::config().await; #[allow(clippy::needless_late_init)] - let resp: Result; + let resp: Result; match payload.notification { PayloadKind::FRReceived(alert) => { @@ -100,13 +97,16 @@ impl AsyncConsumer for FcmOutboundConsumer { .unwrap(); let mut data = HashMap::new(); - data.insert("type", "push.fr.receive"); - data.insert("id", &alert.from_user.id); - data.insert("username", &name); + data.insert( + "type".to_string(), + Value::String("push.fr.receive".to_string()), + ); + data.insert("id".to_string(), Value::String(alert.from_user.id)); + data.insert("username".to_string(), Value::String(name)); let msg = Message { token: Some(payload.token), - data: Some(data), + data: Some(data.into()), ..Default::default() }; @@ -124,10 +124,13 @@ impl AsyncConsumer for FcmOutboundConsumer { .clone() .unwrap(); - let mut data = HashMap::new(); - data.insert("type", "push.fr.accepted"); - data.insert("id", &alert.accepted_user.id); - data.insert("username", &name); + let mut data: HashMap = HashMap::new(); + data.insert( + "type".to_string(), + Value::String("push.fr.accept".to_string()), + ); + data.insert("id".to_string(), Value::String(alert.accepted_user.id)); + data.insert("username".to_string(), Value::String(name)); let msg = Message { token: Some(payload.token), @@ -168,7 +171,7 @@ impl AsyncConsumer for FcmOutboundConsumer { ..Default::default() }; - resp = self.client.send(message_builder.finalize()).await; + resp = self.client.send(&msg).await; } } diff --git a/crates/daemons/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs index 9ec95da68..33ab47759 100644 --- a/crates/daemons/pushd/src/main.rs +++ b/crates/daemons/pushd/src/main.rs @@ -18,7 +18,6 @@ use log::info; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { - //log::set_max_level(log::LevelFilter::Trace); let config = config().await; // Setup database @@ -37,11 +36,21 @@ async fn main() { let mut connections: Vec<(Channel, Connection)> = Vec::new(); + // An explainer of how this works: + // The inbound connections are on separate routing keys, such that they only receive the proper payload + // from their respective api (prod or test). + // However, the outbound queues that go to the services are routed to receive from both, so that messages + // sent from beta are still notified on prod, and vice versa. + + // This'll require some interesting shimming if we need to add more events once this is in prod (different payloads between prod and test), + // but that sounds like a problem for future us. + // inbound: generic connections.push( make_queue_and_consume( &config, &config.pushd.generic_queue, + config.pushd.get_generic_routing_key().as_str(), GenericConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -52,6 +61,7 @@ async fn main() { make_queue_and_consume( &config, &config.pushd.message_queue, + config.pushd.get_message_routing_key().as_str(), MessageConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -62,6 +72,7 @@ async fn main() { make_queue_and_consume( &config, &config.pushd.fr_received_queue, + config.pushd.get_fr_received_routing_key().as_str(), FRReceivedConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -72,6 +83,7 @@ async fn main() { make_queue_and_consume( &config, &config.pushd.fr_accepted_queue, + config.pushd.get_fr_accepted_routing_key().as_str(), FRAcceptedConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -82,17 +94,19 @@ async fn main() { make_queue_and_consume( &config, &config.pushd.apn.queue, + &config.pushd.apn.queue, ApnsOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, ); } - if !config.pushd.fcm.api_key.is_empty() { + if !config.pushd.fcm.auth_uri.is_empty() { connections.push( make_queue_and_consume( &config, &config.pushd.fcm.queue, + &config.pushd.fcm.queue, FcmOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, @@ -104,6 +118,7 @@ async fn main() { make_queue_and_consume( &config, &config.pushd.vapid.queue, + &config.pushd.vapid.queue, VapidOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, @@ -121,7 +136,8 @@ async fn main() { async fn make_queue_and_consume( config: &Settings, - name: &str, + queue_name: &str, + routing_key: &str, consumer: F, ) -> (Channel, Connection) where @@ -138,23 +154,38 @@ where let channel = connection.open_channel(None).await.unwrap(); - let args = QueueDeclareArguments::new(name).durable(true).finish(); - let (queue_name, _, _) = channel.queue_declare(args).await.unwrap().unwrap(); + let mut queue_name = queue_name.to_string(); + + if config.pushd.production { + queue_name += "-prd"; + } else { + queue_name += "-tst"; + } + + let queue_name = queue_name.as_str(); + + let args = QueueDeclareArguments::new(queue_name) + .durable(true) + .finish(); + _ = channel.queue_declare(args).await.unwrap().unwrap(); channel .queue_bind(QueueBindArguments::new( - &queue_name, + queue_name, &config.pushd.exchange, - name, + routing_key, )) .await .unwrap(); - let args = BasicConsumeArguments::new(name, "") + let args = BasicConsumeArguments::new(queue_name, "") .manual_ack(false) .finish(); channel.basic_consume(consumer, args).await.unwrap(); - info!("Consuming queue {}", name); + info!( + "Consuming routing key {} as queue {}", + routing_key, queue_name + ); (channel, connection) } From 7d6ed7a9b9b1c4b3eb249179b857dbd9251aa857 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 4 Sep 2024 16:41:19 -0700 Subject: [PATCH 12/44] add pushd to docker --- Dockerfile | 1 + compose.yml | 19 +++++++++++++------ scripts/build-image-layer.sh | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0127c0a50..965460c92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,7 @@ COPY crates/core/presence/Cargo.toml ./crates/core/presence/ COPY crates/core/result/Cargo.toml ./crates/core/result/ COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/ COPY crates/services/january/Cargo.toml ./crates/services/january/ +COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/ RUN sh /tmp/build-image-layer.sh deps # Build all apps diff --git a/compose.yml b/compose.yml index f8edcf7b9..abbdab2e1 100644 --- a/compose.yml +++ b/compose.yml @@ -35,9 +35,16 @@ services: MINIO_ROOT_USER: minioautumn MINIO_ROOT_PASSWORD: minioautumn entrypoint: > - /bin/sh -c " - /usr/bin/mc config host add minio http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD; - while ! /usr/bin/mc ready minio; do echo 'Waiting minio...' && sleep 1; done; - /usr/bin/mc mb minio/revolt-uploads; - exit 0; - " + /bin/sh -c " /usr/bin/mc config host add minio http://minio:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD; while ! /usr/bin/mc ready minio; do echo 'Waiting minio...' && sleep 1; done; /usr/bin/mc mb minio/revolt-uploads; exit 0; " + + # Rabbit + rabbit: + image: rabbitmq:3-management + environment: + RABBITMQ_DEFAULT_USER: rabbituser + RABBITMQ_DEFAULT_PASS: rabbitpass + volumes: + - ./.data/rabbit:/var/lib/rabbitmq + ports: + - "5672:5672" + - "15672:15672" # management UI, for development diff --git a/scripts/build-image-layer.sh b/scripts/build-image-layer.sh index 5043b09ba..4b2e18c47 100644 --- a/scripts/build-image-layer.sh +++ b/scripts/build-image-layer.sh @@ -33,12 +33,14 @@ deps() { crates/core/presence/src \ crates/core/result/src \ crates/services/autumn/src \ - crates/services/january/src + crates/services/january/src \ + crates/daemons/pushd/src echo 'fn main() { panic!("stub"); }' | tee crates/bonfire/src/main.rs | tee crates/delta/src/main.rs | tee crates/services/autumn/src/main.rs | - tee crates/services/january/src/main.rs + tee crates/services/january/src/main.rs | + tee crates/daemons/pushd/src/main.rs echo '' | tee crates/bindings/node/src/lib.rs | tee crates/core/config/src/lib.rs | @@ -60,6 +62,7 @@ apps() { touch -am \ crates/bonfire/src/main.rs \ crates/delta/src/main.rs \ + crates/daemons/pushd/src/main.rs \ crates/core/config/src/lib.rs \ crates/core/database/src/lib.rs \ crates/core/models/src/lib.rs \ From 68324b17e7ec7e2c4ca2e6ae0d637df18c82e231 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 21 Sep 2024 16:33:46 -0700 Subject: [PATCH 13/44] mix: Remove old code, add stuff to pushd --- compose.yml | 3 + crates/core/config/Revolt.toml | 2 + crates/core/config/src/lib.rs | 1 + crates/core/database/src/amqp/amqp.rs | 39 ++- crates/core/database/src/events/rabbit.rs | 8 + .../src/models/channel_unreads/ops.rs | 3 + .../src/models/channel_unreads/ops/mongodb.rs | 13 +- .../models/channel_unreads/ops/reference.rs | 19 +- .../database/src/models/channels/model.rs | 7 +- .../database/src/models/messages/model.rs | 49 +-- .../core/database/src/models/users/rocket.rs | 2 +- crates/core/database/src/tasks/ack.rs | 49 ++- .../database/src/tasks/apple_notifications.rs | 312 ------------------ crates/core/database/src/tasks/mod.rs | 10 +- crates/core/database/src/tasks/web_push.rs | 248 -------------- crates/core/database/src/util/idempotency.rs | 4 +- .../pushd/src/consumers/inbound/ack.rs | 135 ++++++++ .../pushd/src/consumers/inbound/mod.rs | 1 + .../pushd/src/consumers/outbound/apn.rs | 15 +- .../pushd/src/consumers/outbound/fcm.rs | 6 +- .../pushd/src/consumers/outbound/vapid.rs | 3 + crates/daemons/pushd/src/main.rs | 44 ++- .../src/routes/channels/group_add_member.rs | 5 +- .../routes/channels/group_remove_member.rs | 5 +- .../delta/src/routes/channels/message_send.rs | 4 +- 25 files changed, 344 insertions(+), 643 deletions(-) delete mode 100644 crates/core/database/src/tasks/apple_notifications.rs delete mode 100644 crates/core/database/src/tasks/web_push.rs create mode 100644 crates/daemons/pushd/src/consumers/inbound/ack.rs diff --git a/compose.yml b/compose.yml index abbdab2e1..f50aeac4c 100644 --- a/compose.yml +++ b/compose.yml @@ -45,6 +45,9 @@ services: RABBITMQ_DEFAULT_PASS: rabbitpass volumes: - ./.data/rabbit:/var/lib/rabbitmq + #- ./rabbit_plugins:/opt/rabbitmq/plugins/ + #- ./rabbit_enabled_plugins:/etc/rabbitmq/enabled_plugins + # uncomment this if you need to enable other plugins ports: - "5672:5672" - "15672:15672" # management UI, for development diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index 02d9c49a2..69d00fd29 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -68,11 +68,13 @@ max_concurrent_connections = 50 # Usually they have to be, so that messages sent from one or the other get sent to everyone production = true +# none of these should need changing exchange = "revolt.notifications" message_queue = "notifications.origin.message" fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted fr_received_queue = "notifications.ingest.fr_received" # friend request received generic_queue = "notifications.ingest.generic" # generic messages (title + body) +ack_queue = "notifications.process.ack" # updates badges for apple devices [pushd.vapid] queue = "notifications.outbound.vapid" diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index c8ea38c2d..74f5768b9 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -142,6 +142,7 @@ pub struct Pushd { pub fr_accepted_queue: String, pub fr_received_queue: String, pub generic_queue: String, + pub ack_queue: String, pub vapid: PushVapid, pub fcm: PushFcm, diff --git a/crates/core/database/src/amqp/amqp.rs b/crates/core/database/src/amqp/amqp.rs index 50fadd714..25c763bd8 100644 --- a/crates/core/database/src/amqp/amqp.rs +++ b/crates/core/database/src/amqp/amqp.rs @@ -4,7 +4,7 @@ use crate::events::rabbit::*; use crate::User; use amqprs::channel::BasicPublishArguments; use amqprs::BasicProperties; -use amqprs::{channel::Channel, connection::Connection}; +use amqprs::{channel::Channel, connection::Connection, error::Error as AMQPError}; use revolt_models::v0::PushNotification; use revolt_presence::filter_online; @@ -29,7 +29,7 @@ impl AMQP { &self, accepted_request_user: &User, sent_request_user: &User, - ) -> Result<(), amqprs::error::Error> { + ) -> Result<(), AMQPError> { let config = revolt_config::config().await; let payload = FRAcceptedPayload { accepted_user: accepted_request_user.to_owned(), @@ -55,7 +55,7 @@ impl AMQP { &self, received_request_user: &User, sent_request_user: &User, - ) -> Result<(), amqprs::error::Error> { + ) -> Result<(), AMQPError> { let config = revolt_config::config().await; let payload = FRReceivedPayload { from_user: sent_request_user.to_owned(), @@ -83,7 +83,7 @@ impl AMQP { title: String, body: String, icon: Option, - ) -> Result<(), amqprs::error::Error> { + ) -> Result<(), AMQPError> { let config = revolt_config::config().await; let payload = GenericPayload { title, @@ -109,12 +109,12 @@ impl AMQP { &self, recipients: Vec, payload: PushNotification, - ) -> Result<(), amqprs::error::Error> { + ) -> Result<(), AMQPError> { if recipients.is_empty() { return Ok(()); } - let config: revolt_config::Settings = revolt_config::config().await; + let config = revolt_config::config().await; let online_ids = filter_online(&recipients).await; let recipients = (&recipients.into_iter().collect::>() - &online_ids) @@ -138,4 +138,31 @@ impl AMQP { ) .await } + + pub async fn ack_message( + &self, + user_id: String, + channel_id: String, + message_id: String, + ) -> Result<(), AMQPError> { + let config = revolt_config::config().await; + + let payload = AckPayload { + user_id, + channel_id, + message_id, + }; + let payload = to_string(&payload).unwrap(); + + self.channel + .basic_publish( + BasicProperties::default() + .with_content_type("application/json") + .with_persistence(true) + .finish(), + payload.into(), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue), + ) + .await + } } diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs index acfdb2806..613047521 100644 --- a/crates/core/database/src/events/rabbit.rs +++ b/crates/core/database/src/events/rabbit.rs @@ -38,6 +38,7 @@ pub enum PayloadKind { MessageNotification(PushNotification), FRAccepted(FRAcceptedPayload), FRReceived(FRReceivedPayload), + BadgeUpdate(usize), Generic(GenericPayload), } @@ -49,3 +50,10 @@ pub struct PayloadToService { pub token: String, pub extras: HashMap, } + +#[derive(Serialize, Deserialize)] +pub struct AckPayload { + pub user_id: String, + pub channel_id: String, + pub message_id: String, +} diff --git a/crates/core/database/src/models/channel_unreads/ops.rs b/crates/core/database/src/models/channel_unreads/ops.rs index 4d115069e..6a6e98af3 100644 --- a/crates/core/database/src/models/channel_unreads/ops.rs +++ b/crates/core/database/src/models/channel_unreads/ops.rs @@ -26,6 +26,9 @@ pub trait AbstractChannelUnreads: Sync + Send { message_ids: &[String], ) -> Result<()>; + /// Fetch all unreads with mentions for a user. + async fn fetch_unread_mentions(&self, user_id: &str) -> Result>; + /// Fetch all channel unreads for a user. async fn fetch_unreads(&self, user_id: &str) -> Result>; diff --git a/crates/core/database/src/models/channel_unreads/ops/mongodb.rs b/crates/core/database/src/models/channel_unreads/ops/mongodb.rs index e2dfedb17..c237afc65 100644 --- a/crates/core/database/src/models/channel_unreads/ops/mongodb.rs +++ b/crates/core/database/src/models/channel_unreads/ops/mongodb.rs @@ -123,6 +123,18 @@ impl AbstractChannelUnreads for MongoDb { ) } + async fn fetch_unread_mentions(&self, user_id: &str) -> Result> { + query! { + self, + find, + COL, + doc! { + "_id.user": user_id, + "mentions": {"$ne": null} + } + } + } + /// Fetch unread for a specific user in a channel. async fn fetch_unread(&self, user_id: &str, channel_id: &str) -> Result> { query!( @@ -135,5 +147,4 @@ impl AbstractChannelUnreads for MongoDb { } ) } - } diff --git a/crates/core/database/src/models/channel_unreads/ops/reference.rs b/crates/core/database/src/models/channel_unreads/ops/reference.rs index b8a95d03e..914ac95db 100644 --- a/crates/core/database/src/models/channel_unreads/ops/reference.rs +++ b/crates/core/database/src/models/channel_unreads/ops/reference.rs @@ -78,6 +78,15 @@ impl AbstractChannelUnreads for ReferenceDb { Ok(()) } + async fn fetch_unread_mentions(&self, user_id: &str) -> Result> { + let unreads = self.channel_unreads.lock().await; + Ok(unreads + .values() + .filter(|unread| unread.id.user == user_id && unread.mentions.is_some()) + .cloned() + .collect()) + } + /// Fetch all channel unreads for a user. async fn fetch_unreads(&self, user_id: &str) -> Result> { let unreads = self.channel_unreads.lock().await; @@ -92,9 +101,11 @@ impl AbstractChannelUnreads for ReferenceDb { async fn fetch_unread(&self, user_id: &str, channel_id: &str) -> Result> { let unreads = self.channel_unreads.lock().await; - Ok(unreads.get(&ChannelCompositeKey { - channel: channel_id.to_string(), - user: user_id.to_string() - }).cloned()) + Ok(unreads + .get(&ChannelCompositeKey { + channel: channel_id.to_string(), + user: user_id.to_string(), + }) + .cloned()) } } diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index cd065818d..130952c97 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -9,7 +9,7 @@ use ulid::Ulid; use crate::{ events::client::EventV1, tasks::ack::AckEvent, Database, File, IntoDocumentPath, PartialServer, - Server, SystemMessage, User, + Server, SystemMessage, User, AMQP, }; auto_derived!( @@ -337,6 +337,7 @@ impl Channel { pub async fn add_user_to_group( &mut self, db: &Database, + amqp: &AMQP, user: &User, by_id: &str, ) -> Result<()> { @@ -373,6 +374,7 @@ impl Channel { .into_message(id.to_string()) .send( db, + amqp, MessageAuthor::System { username: &user.username, avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), @@ -655,6 +657,7 @@ impl Channel { pub async fn remove_user_from_group( &self, db: &Database, + amqp: &AMQP, user: &User, by_id: Option<&str>, silent: bool, @@ -686,6 +689,7 @@ impl Channel { .into_message(id.to_string()) .send( db, + amqp, MessageAuthor::System { username: name, avatar: None, @@ -725,6 +729,7 @@ impl Channel { .into_message(id.to_string()) .send( db, + amqp, MessageAuthor::System { username: &user.username, avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 2d3ef99fe..fa9a12b88 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, ops::Deref}; +use std::collections::HashSet; use indexmap::{IndexMap, IndexSet}; use iso8601_timestamp::Timestamp; @@ -16,7 +16,7 @@ use crate::{ events::client::EventV1, tasks::{self, ack::AckEvent}, util::idempotency::IdempotencyKey, - Channel, Database, Emoji, File, User, + Channel, Database, Emoji, File, User, AMQP, }; auto_derived_partial!( @@ -230,6 +230,7 @@ impl Message { #[allow(clippy::too_many_arguments)] pub async fn create_from_api( db: &Database, + amqp: &AMQP, channel: Channel, data: DataMessageSend, author: MessageAuthor<'_>, @@ -393,7 +394,7 @@ impl Message { // Send the message message - .send(db, author, user, member, &channel, generate_embeds) + .send(db, amqp, author, user, member, &channel, generate_embeds) .await?; Ok(message) @@ -448,9 +449,11 @@ impl Message { } /// Send a message + #[allow(clippy::too_many_arguments)] pub async fn send( &mut self, db: &Database, + amqp: &AMQP, author: MessageAuthor<'_>, user: Option, member: Option, @@ -467,24 +470,30 @@ impl Message { .await?; if !self.has_suppressed_notifications() { - // Push out Web Push notifications - crate::tasks::web_push::queue( - { - match channel { - Channel::DirectMessage { recipients, .. } - | Channel::Group { recipients, .. } => recipients.clone(), - Channel::TextChannel { .. } => self.mentions.clone().unwrap_or_default(), - _ => vec![], - } - }, - PushNotification::from( - self.clone().into_model(user, member), - Some(author), - channel.to_owned().into(), + // send Push notifications + if let Err(resp) = amqp + .message_sent( + { + match channel { + Channel::DirectMessage { recipients, .. } + | Channel::Group { recipients, .. } => recipients.clone(), + Channel::TextChannel { .. } => { + self.mentions.clone().unwrap_or_default() + } + _ => vec![], + } + }, + PushNotification::from( + self.clone().into_model(user, member), + Some(author), + channel.to_owned().into(), + ) + .await, ) - .await, - ) - .await; + .await + { + revolt_config::capture_error(&resp); + } } Ok(()) diff --git a/crates/core/database/src/models/users/rocket.rs b/crates/core/database/src/models/users/rocket.rs index ab8866947..57b634b15 100644 --- a/crates/core/database/src/models/users/rocket.rs +++ b/crates/core/database/src/models/users/rocket.rs @@ -38,7 +38,7 @@ impl<'r> FromRequest<'r> for User { if let Some(user) = user { Outcome::Success(user.clone()) } else { - Outcome::Failure((Status::Unauthorized, authifier::Error::InvalidSession)) + Outcome::Error((Status::Unauthorized, authifier::Error::InvalidSession)) } } } diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index b5c90fdd7..d50c2c55b 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -1,5 +1,5 @@ // Queue Type: Debounced -use crate::Database; +use crate::{Database, AMQP}; use deadqueue::limited::Queue; use once_cell::sync::Lazy; @@ -7,7 +7,10 @@ use std::{collections::HashMap, time::Duration}; use revolt_result::Result; -use super::{apple_notifications::{self, ApnJob}, DelayedTask}; +use super::{ + //apple_notifications::{self, ApnJob}, + DelayedTask, +}; /// Enumeration of possible events #[derive(Debug, Eq, PartialEq)] @@ -54,33 +57,27 @@ pub async fn queue(channel: String, user: String, event: AckEvent) { info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); } -pub async fn handle_ack_event(event: &AckEvent, db: &Database, authifier_db: &authifier::Database, user: &str, channel: &str) -> Result<()> { +pub async fn handle_ack_event( + event: &AckEvent, + db: &Database, + amqp: &AMQP, + user: &str, + channel: &str, +) -> Result<()> { match &event { #[allow(clippy::disallowed_methods)] // event is sent by higher level function AckEvent::AckMessage { id } => { - let unread = db.fetch_unread(user, channel).await?; - let updated = db.acknowledge_message(channel, user, id).await?; - - if let (Some(before), Some(after)) = (unread, updated) { - let before_mentions = before.mentions.unwrap_or_default().len(); - let after_mentions = after.mentions.unwrap_or_default().len(); - - let mentions_acked = before_mentions - after_mentions; - - if mentions_acked > 0 { - if let Ok(sessions) = authifier_db.find_sessions(user).await { - for session in sessions { - if let Some(sub) = session.subscription { - if sub.endpoint == "apn" { - apple_notifications::queue(ApnJob::from_ack(session.id, user.to_string(), sub.auth)).await; - } - } - } - } - }; + if let Err(resp) = db.acknowledge_message(channel, user, id).await { + revolt_config::capture_error(&resp); + } + if let Err(resp) = amqp + .ack_message(user.into(), channel.into(), id.into()) + .await + { + revolt_config::capture_error(&resp); } - }, + } AckEvent::AddMention { ids } => { db.add_mention_to_unread(channel, user, ids).await?; } @@ -90,7 +87,7 @@ pub async fn handle_ack_event(event: &AckEvent, db: &Database, authifier_db: &au } /// Start a new worker -pub async fn worker(db: Database, authifier_db: authifier::Database) { +pub async fn worker(db: Database, amqp: AMQP) { let mut tasks = HashMap::<(String, String), DelayedTask>::new(); let mut keys = vec![]; @@ -108,7 +105,7 @@ pub async fn worker(db: Database, authifier_db: authifier::Database) { let Task { event } = task.data; let (user, channel) = key; - if let Err(err) = handle_ack_event(&event, &db, &authifier_db, user, channel).await { + if let Err(err) = handle_ack_event(&event, &db, &amqp, user, channel).await { error!("{err:?} for {event:?}. ({user}, {channel})"); } else { info!("User {user} ack in {channel} with {event:?}"); diff --git a/crates/core/database/src/tasks/apple_notifications.rs b/crates/core/database/src/tasks/apple_notifications.rs deleted file mode 100644 index c9ef5d5c7..000000000 --- a/crates/core/database/src/tasks/apple_notifications.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::io::Cursor; - -use base64::{ - engine::{self}, - Engine as _, -}; -use deadqueue::limited::Queue; -use once_cell::sync::Lazy; -use revolt_a2::{ - request::{ - notification::{DefaultAlert, NotificationOptions}, - payload::{APSAlert, APSSound, PayloadLike, APS}, - }, - Client, ClientConfig, Endpoint, Error, ErrorBody, ErrorReason, Priority, PushType, Response, -}; -use revolt_config::config; -use revolt_models::v0::{Message, PushNotification}; - -use crate::Database; - -/// Payload information, before assembly -#[derive(Debug)] -pub struct ApnPayload { - message: Message, - url: String, - authorAvatar: String, - authorDisplayName: String, - channelName: String, -} - -#[derive(Serialize, Debug)] -struct Payload<'a> { - aps: APS<'a>, - #[serde(skip_serializing)] - options: NotificationOptions<'a>, - #[serde(skip_serializing)] - device_token: &'a str, - - message: &'a Message, - url: &'a str, - authorAvatar: &'a str, - authorDisplayName: &'a str, - channelName: &'a str, -} - -impl<'a> PayloadLike for Payload<'a> { - fn get_device_token(&self) -> &'a str { - self.device_token - } - fn get_options(&self) -> &NotificationOptions { - &self.options - } -} - -/// Task information -#[derive(Debug)] -pub struct AlertJob { - /// Session Id - session_id: String, - - /// Device token - device_token: String, - - /// User Id - user_id: String, - - /// Title - title: String, - - /// Body - body: String, - - /// Thread Id - thread_id: String, - - /// Category (informs the client what kind of notification is being sent.) - category: String, - - /// Payload used by the iOS client to modify the notification - custom_payload: ApnPayload, -} - -impl AlertJob { - fn format_title(notification: &PushNotification) -> String { - // ideally this changes depending on context - // in a server, it would look like "Sendername, #channelname in servername" - // in a group, it would look like "Sendername in groupname" - // in a dm it should just be "Sendername". - // not sure how feasible all those are given the PushNotification object as it currently stands. - format!( - "{} in {}", - notification.author, notification.message.channel - ) // TODO: this absolutely needs a channel name - } -} - -#[derive(Debug)] -pub struct BadgeJob { - /// Session Id - session_id: String, - - /// Device token - device_token: String, - - /// User Id - user_id: String, -} - -#[derive(Debug)] -pub enum JobType { - Alert(AlertJob), - Badge(BadgeJob), -} - -#[derive(Debug)] -pub struct ApnJob { - job_type: JobType, -} - -impl ApnJob { - pub fn from_notification( - session_id: String, - user_id: String, - device_token: String, - notification: &PushNotification, - ) -> ApnJob { - ApnJob { - job_type: JobType::Alert(AlertJob { - session_id, - device_token, - user_id, - title: AlertJob::format_title(notification), - body: notification.body.to_string(), - thread_id: notification.tag.to_string(), - category: "ALERT_MESSAGE".to_string(), - custom_payload: ApnPayload { - message: notification.message.clone(), - url: notification.url.clone(), - authorAvatar: notification.icon.clone(), - authorDisplayName: notification.author.clone(), - channelName: "#fetchchannelnamehere".to_string(), // TODO: get actual channel name - }, - }), - } - } - - pub fn from_ack(session_id: String, user_id: String, device_token: String) -> ApnJob { - ApnJob { - job_type: JobType::Badge(BadgeJob { - session_id, - device_token, - user_id, - }), - } - } -} - -enum AssembledPayload<'a> { - Alert(Payload<'a>), - Default(revolt_a2::request::payload::Payload<'a>), -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - -/// Queue a new task for a worker -pub async fn queue(task: ApnJob) { - Q.try_push(task).ok(); - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -async fn get_badge_count(db: &Database, user: &str) -> Option { - if let Ok(unreads) = db.fetch_unreads(user).await { - let mut mention_count = 0; - for channel in unreads { - if let Some(mentions) = channel.mentions { - mention_count += mentions.len() as u32 - } - } - - return Some(mention_count); - } - None -} - -/// Start a new worker -pub async fn worker(db: Database) { - let config = config().await; - if config.pushd.apn.pkcs8.is_empty() - || config.pushd.apn.key_id.is_empty() - || config.pushd.apn.team_id.is_empty() - { - eprintln!("Missing APN keys."); - return; - } - - let endpoint = if config.pushd.apn.sandbox { - Endpoint::Sandbox - } else { - Endpoint::Production - }; - - let pkcs8 = engine::general_purpose::STANDARD - .decode(config.pushd.apn.pkcs8) - .expect("valid `pcks8`"); - - let client_config = ClientConfig::new(endpoint); - - let client = Client::token( - &mut Cursor::new(pkcs8), - config.pushd.apn.key_id, - config.pushd.apn.team_id, - client_config, - ) - .expect("could not create APN client"); - - let payload_options = NotificationOptions { - apns_id: None, - apns_push_type: Some(PushType::Alert), - apns_expiration: None, - apns_priority: Some(Priority::High), - apns_topic: Some("chat.revolt.app"), - apns_collapse_id: None, - }; - - loop { - let task = Q.pop().await; - let payload: AssembledPayload; - - match task.job_type { - JobType::Alert(ref alert) => { - payload = AssembledPayload::Alert(Payload { - aps: APS { - alert: Some(APSAlert::Default(DefaultAlert { - title: Some(&alert.title), - subtitle: None, - body: Some(&alert.body), - title_loc_key: None, - title_loc_args: None, - action_loc_key: None, - loc_key: None, - loc_args: None, - launch_image: None, - })), - badge: get_badge_count(&db, &alert.user_id).await, - sound: Some(APSSound::Sound("default")), - thread_id: Some(&alert.thread_id), - content_available: None, - category: Some(&alert.category), - mutable_content: Some(1), - url_args: None, - }, - device_token: &alert.device_token, - options: payload_options.clone(), - message: &alert.custom_payload.message, - url: &alert.custom_payload.url, - authorAvatar: &alert.custom_payload.authorAvatar, - authorDisplayName: &alert.custom_payload.authorDisplayName, - channelName: &alert.custom_payload.channelName, - }); - } - JobType::Badge(ref alert) => { - payload = AssembledPayload::Default(revolt_a2::request::payload::Payload { - aps: APS { - alert: None, - badge: get_badge_count(&db, &alert.user_id).await, - sound: None, - thread_id: None, - content_available: None, - category: None, - mutable_content: None, - url_args: None, - }, - device_token: &alert.device_token, - options: payload_options.clone(), - data: std::collections::BTreeMap::new(), - }) - } - } - - let resp = match payload { - AssembledPayload::Alert(p) => client.send(p).await, - AssembledPayload::Default(p) => client.send(p).await, - }; - //println!("response from APNS: {:?}", resp); - - if let Err(err) = resp { - match err { - Error::ResponseError(Response { - error: - Some(ErrorBody { - reason: ErrorReason::BadDeviceToken | ErrorReason::Unregistered, - .. - }), - .. - }) => { - if let Err(err) = db - .remove_push_subscription_by_session_id(match task.job_type { - JobType::Alert(ref a) => &a.session_id.as_str(), - JobType::Badge(ref a) => &a.session_id.as_str(), - }) - .await - { - revolt_config::capture_error(&err); - } - } - err => { - revolt_config::capture_error(&err); - } - } - } - } -} diff --git a/crates/core/database/src/tasks/mod.rs b/crates/core/database/src/tasks/mod.rs index d89887563..81bfd4e1f 100644 --- a/crates/core/database/src/tasks/mod.rs +++ b/crates/core/database/src/tasks/mod.rs @@ -1,6 +1,6 @@ //! Semi-important background task management -use crate::Database; +use crate::{Database, AMQP}; use async_std::task; use std::time::Instant; @@ -8,22 +8,18 @@ use std::time::Instant; const WORKER_COUNT: usize = 5; pub mod ack; -pub mod apple_notifications; pub mod authifier_relay; pub mod last_message_id; pub mod process_embeds; -pub mod web_push; /// Spawn background workers -pub fn start_workers(db: Database, authifier_db: authifier::Database) { +pub fn start_workers(db: Database, amqp: AMQP) { task::spawn(authifier_relay::worker()); - task::spawn(apple_notifications::worker(db.clone())); for _ in 0..WORKER_COUNT { - task::spawn(ack::worker(db.clone(), authifier_db.clone())); + task::spawn(ack::worker(db.clone(), amqp.clone())); task::spawn(last_message_id::worker(db.clone())); task::spawn(process_embeds::worker(db.clone())); - task::spawn(web_push::worker(db.clone(), authifier_db.clone())); } } diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs deleted file mode 100644 index cfd83017f..000000000 --- a/crates/core/database/src/tasks/web_push.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - time::Duration, -}; - -use amqprs::{ - channel::BasicPublishArguments, - connection::{Connection, OpenConnectionArguments}, - BasicProperties, -}; -use authifier::Database as AuthifierDatabase; -use base64::{ - engine::{self}, - Engine as _, -}; -use deadqueue::limited::Queue; -use fcm_v1::auth::{Authenticator, ServiceAccountKey}; -use once_cell::sync::Lazy; -use revolt_config::config; -use revolt_models::v0::PushNotification; -use revolt_presence::filter_online; -use serde_json::{json, to_string}; -use web_push::{ - ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, - WebPushClient, WebPushMessageBuilder, -}; - -use crate::Database; - -use super::apple_notifications; -use crate::events::rabbit::{self, MessageSentPayload}; - -/// Task information -#[derive(Debug)] -struct PushTask { - /// User IDs of the targets that are to receive this notification - recipients: Vec, - /// Push Notification - payload: PushNotification, -} - -static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); - -/// Queue a new task for a worker -pub async fn queue(recipients: Vec, payload: PushNotification) { - if recipients.is_empty() { - return; - } - - let online_ids = filter_online(&recipients).await; - let recipients = (&recipients.into_iter().collect::>() - &online_ids) - .into_iter() - .collect::>(); - - Q.try_push(PushTask { - recipients, - payload, - }) - .ok(); - - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); -} - -/// Start a new worker -pub async fn worker(db: Database, authifier_db: AuthifierDatabase) { - let config = config().await; - - // let web_push_client = IsahcWebPushClient::new().unwrap(); - // let fcm_client = if config.api.fcm.key_type.is_empty() { - // None - // } else { - // Some(fcm_v1::Client::new( - // Authenticator::service_account::<&str>(ServiceAccountKey { - // key_type: Some(config.api.fcm.key_type), - // project_id: Some(config.api.fcm.project_id.clone()), - // private_key_id: Some(config.api.fcm.private_key_id), - // private_key: config.api.fcm.private_key, - // client_email: config.api.fcm.client_email, - // client_id: Some(config.api.fcm.client_id), - // auth_uri: Some(config.api.fcm.auth_uri), - // token_uri: config.api.fcm.token_uri, - // auth_provider_x509_cert_url: Some(config.api.fcm.auth_provider_x509_cert_url), - // client_x509_cert_url: Some(config.api.fcm.client_x509_cert_url), - // }) - // .await - // .unwrap(), - // config.api.fcm.project_id, - // false, - // Duration::from_secs(5), - // )) - // }; - - // let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD - // .decode(config.pushd.vapid.private_key) - // .expect("valid `VAPID_PRIVATE_KEY`"); - - let conn = Connection::open(&OpenConnectionArguments::new( - &config.rabbit.host, - config.rabbit.port, - &config.rabbit.username, - &config.rabbit.password, - )) - .await - .expect("Failed to create the AMQP connection"); - - let channel = conn - .open_channel(None) - .await - .expect("Failed to create an AMQP channel"); - - let basic_properties = BasicProperties::default() - .with_content_type("application/json") - .with_persistence(true) - .finish(); - - let publish_arguments = - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue); - - loop { - let task = Q.pop().await; - - let payload = MessageSentPayload { - notification: task.payload, - users: task.recipients, - }; - - if let Err(err) = channel - .basic_publish( - basic_properties.clone(), - to_string(&payload).unwrap().into_bytes(), - publish_arguments.clone(), - ) - .await - { - revolt_config::capture_error(&err); - error!("Failed to send notification") - } else { - debug!( - "Sent message to {} on exchange {}", - config.pushd.message_queue, config.pushd.exchange - ); - - // if let Ok(sessions) = authifier_db - // .find_sessions_with_subscription(&task.recipients) - // .await - // { - // for session in sessions { - // if let Some(sub) = session.subscription { - // if sub.endpoint == "fcm" { - // // Use Firebase Cloud Messaging - // if let Some(client) = &fcm_client { - // let message = fcm_v1::message::Message { - // token: Some(sub.auth), - // data: Some(HashMap::from([( - // "payload".to_owned(), - // serde_json::Value::String(json!(&task.payload).to_string()), - // )])), - // ..Default::default() - // }; - - // if let Err(err) = client.send(&message).await { - // error!("Failed to send FCM notification! {:?}", err); - - // if let fcm_v1::Error::FCM(fcm_error) = err { - // if fcm_error.contains("404 (Not Found)") { - // println!("Unregistering {:?}", session.id); - - // if let Err(err) = db - // .remove_push_subscription_by_session_id(&session.id) - // .await - // { - // revolt_config::capture_error(&err); - // } - // } - // } - // } else { - // info!("Sent FCM notification to {:?}.", session.id); - // } - // } else { - // info!("No FCM token was specified!"); - // } - // } else if sub.endpoint == "apn" { - // apple_notifications::queue(apple_notifications::ApnJob::from_notification( - // session.id, - // session.user_id, - // sub.auth, - // &task.payload, - // )) - // .await; - // } else { - // // Use Web Push Standard - // let subscription = SubscriptionInfo { - // endpoint: sub.endpoint, - // keys: SubscriptionKeys { - // auth: sub.auth, - // p256dh: sub.p256dh, - // }, - // }; - - // match VapidSignatureBuilder::from_pem( - // std::io::Cursor::new(&web_push_private_key), - // &subscription, - // ) { - // Ok(sig_builder) => match sig_builder.build() { - // Ok(signature) => { - // let mut builder = WebPushMessageBuilder::new(&subscription); - // builder.set_vapid_signature(signature); - - // let payload = json!(task.payload).to_string(); - // builder - // .set_payload(ContentEncoding::AesGcm, payload.as_bytes()); - - // match builder.build() { - // Ok(msg) => match web_push_client.send(msg).await { - // Ok(_) => { - // info!( - // "Sent Web Push notification to {:?}.", - // session.id - // ) - // } - // Err(err) => { - // error!("Hit error sending Web Push! {:?}", err) - // } - // }, - // Err(err) => { - // error!( - // "Failed to build message for {}! {:?}", - // session.user_id, err - // ) - // } - // } - // } - // Err(err) => error!( - // "Failed to build signature for {}! {:?}", - // session.user_id, err - // ), - // }, - // Err(err) => error!( - // "Failed to create signature builder for {}! {:?}", - // session.user_id, err - // ), - // } - // } - // } - // } - } - } -} diff --git a/crates/core/database/src/util/idempotency.rs b/crates/core/database/src/util/idempotency.rs index d95236d77..df68000d9 100644 --- a/crates/core/database/src/util/idempotency.rs +++ b/crates/core/database/src/util/idempotency.rs @@ -102,7 +102,7 @@ impl<'r> FromRequest<'r> for IdempotencyKey { .map(|k| k.to_string()) { if key.len() > 64 { - return Outcome::Failure(( + return Outcome::Error(( Status::BadRequest, create_error!(FailedValidation { error: "idempotency key too long".to_string(), @@ -113,7 +113,7 @@ impl<'r> FromRequest<'r> for IdempotencyKey { let idempotency = IdempotencyKey { key }; let mut cache = TOKEN_CACHE.lock().await; if cache.get(&idempotency.key).is_some() { - return Outcome::Failure((Status::Conflict, create_error!(DuplicateNonce))); + return Outcome::Error((Status::Conflict, create_error!(DuplicateNonce))); } cache.put(idempotency.key.clone(), ()); diff --git a/crates/daemons/pushd/src/consumers/inbound/ack.rs b/crates/daemons/pushd/src/consumers/inbound/ack.rs new file mode 100644 index 000000000..a44de4221 --- /dev/null +++ b/crates/daemons/pushd/src/consumers/inbound/ack.rs @@ -0,0 +1,135 @@ +use crate::consumers::inbound::internal::*; +use amqprs::{ + channel::{BasicPublishArguments, Channel}, + connection::Connection, + consumer::AsyncConsumer, + BasicProperties, Deliver, +}; +use async_trait::async_trait; +use revolt_database::{events::rabbit::*, Database}; + +pub struct AckConsumer { + #[allow(dead_code)] + db: Database, + authifier_db: authifier::Database, + conn: Option, + channel: Option, +} + +impl Channeled for AckConsumer { + fn get_connection(&self) -> Option<&Connection> { + if self.conn.is_none() { + None + } else { + Some(self.conn.as_ref().unwrap()) + } + } + + fn get_channel(&self) -> Option<&Channel> { + if self.channel.is_none() { + None + } else { + Some(self.channel.as_ref().unwrap()) + } + } + + fn set_connection(&mut self, conn: Connection) { + self.conn = Some(conn); + } + + fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } +} + +impl AckConsumer { + pub fn new(db: Database, authifier_db: authifier::Database) -> AckConsumer { + AckConsumer { + db, + authifier_db, + conn: None, + channel: None, + } + } +} + +#[allow(unused_variables)] +#[async_trait] +impl AsyncConsumer for AckConsumer { + /// This consumer processes all acks the platform receives, and sends relevant badge updates to apple platforms. + async fn consume( + &mut self, + channel: &Channel, + deliver: Deliver, + basic_properties: BasicProperties, + content: Vec, + ) { + let content = String::from_utf8(content).unwrap(); + let payload: AckPayload = serde_json::from_str(content.as_str()).unwrap(); + + log::debug!("Received Ack event"); + + // Step 1: fetch unreads and don't continue if there's no unreads + #[allow(clippy::disallowed_methods)] + let unreads = self.db.fetch_unread_mentions(&payload.user_id).await; + + if let Ok(u) = &unreads { + if u.is_empty() { + return; + } + } else { + return; + } + + if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user_id).await { + let config = revolt_config::config().await; + // Step 2: find any apple sessions, since we don't need to calculate this for anything else. + // If there's no apple sessions, we can return early + let apple_sessions: Vec<&authifier::models::Session> = sessions + .iter() + .filter(|session| { + if let Some(sub) = &session.subscription { + sub.endpoint == "apn" + } else { + false + } + }) + .collect(); + + if apple_sessions.is_empty() { + return; + } + + // Step 3: calculate the actual mention count, since we have to send it out + let mut mention_count = 0; + for u in &unreads.unwrap() { + mention_count += u.mentions.as_ref().unwrap().len() + } + + // Step 4: loop through each apple session and send the badge update + for session in apple_sessions { + let service_payload = PayloadToService { + notification: PayloadKind::BadgeUpdate(mention_count), + user_id: payload.user_id.clone(), + session_id: session.id.clone(), + token: session.subscription.as_ref().unwrap().auth.clone(), + extras: Default::default(), + }; + let raw_service_payload = serde_json::to_string(&service_payload); + + if let Ok(p) = raw_service_payload { + let args = BasicPublishArguments::new( + config.pushd.exchange.as_str(), + config.pushd.apn.queue.as_str(), + ) + .finish(); + + publish_message(self, p.into(), args).await; + } else { + log::error!("Failed to serialize ack badge update payload!"); + log::error!("{:?}", raw_service_payload.unwrap_err()) + } + } + } + } +} diff --git a/crates/daemons/pushd/src/consumers/inbound/mod.rs b/crates/daemons/pushd/src/consumers/inbound/mod.rs index 3ca8f184a..a340143d7 100644 --- a/crates/daemons/pushd/src/consumers/inbound/mod.rs +++ b/crates/daemons/pushd/src/consumers/inbound/mod.rs @@ -1,3 +1,4 @@ +pub mod ack; pub mod fr_accepted; pub mod fr_received; pub mod generic; diff --git a/crates/daemons/pushd/src/consumers/outbound/apn.rs b/crates/daemons/pushd/src/consumers/outbound/apn.rs index 111a48e2a..a3e5c8d93 100644 --- a/crates/daemons/pushd/src/consumers/outbound/apn.rs +++ b/crates/daemons/pushd/src/consumers/outbound/apn.rs @@ -73,7 +73,7 @@ impl ApnsOutboundConsumer { } async fn get_badge_count(&self, user: &str) -> Option { - if let Ok(unreads) = self.db.fetch_unreads(user).await { + if let Ok(unreads) = self.db.fetch_unread_mentions(user).await { let mut mention_count = 0; for channel in unreads { if let Some(mentions) = channel.mentions { @@ -292,6 +292,19 @@ impl AsyncConsumer for ApnsOutboundConsumer { channel_name: alert.channel.name(), }; + resp = self.client.send(apn_payload).await; + } + PayloadKind::BadgeUpdate(badge) => { + let apn_payload = Payload { + aps: APS { + badge: Some(badge as u32), + ..Default::default() + }, + device_token: &payload.token, + options: payload_options.clone(), + data: BTreeMap::new(), + }; + resp = self.client.send(apn_payload).await; } } diff --git a/crates/daemons/pushd/src/consumers/outbound/fcm.rs b/crates/daemons/pushd/src/consumers/outbound/fcm.rs index b05afcbf8..61700bbb9 100644 --- a/crates/daemons/pushd/src/consumers/outbound/fcm.rs +++ b/crates/daemons/pushd/src/consumers/outbound/fcm.rs @@ -106,7 +106,7 @@ impl AsyncConsumer for FcmOutboundConsumer { let msg = Message { token: Some(payload.token), - data: Some(data.into()), + data: Some(data), ..Default::default() }; @@ -173,6 +173,10 @@ impl AsyncConsumer for FcmOutboundConsumer { resp = self.client.send(&msg).await; } + + PayloadKind::BadgeUpdate(_) => { + panic!("FCM cannot handle badge updates, and they should not be sent here.") + } } if let Err(err) = resp { diff --git a/crates/daemons/pushd/src/consumers/outbound/vapid.rs b/crates/daemons/pushd/src/consumers/outbound/vapid.rs index 4adcc5ffc..1d386c1a4 100644 --- a/crates/daemons/pushd/src/consumers/outbound/vapid.rs +++ b/crates/daemons/pushd/src/consumers/outbound/vapid.rs @@ -124,6 +124,9 @@ impl AsyncConsumer for VapidOutboundConsumer { PayloadKind::MessageNotification(alert) => { payload_body = serde_json::to_string(&alert).unwrap(); } + PayloadKind::BadgeUpdate(_) => { + panic!("Vapid cannot handle badge updates, and they should not be sent here.") + } } match VapidSignatureBuilder::from_pem(std::io::Cursor::new(&self.pkey), &subscription) { diff --git a/crates/daemons/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs index 33ab47759..05ab72a1f 100644 --- a/crates/daemons/pushd/src/main.rs +++ b/crates/daemons/pushd/src/main.rs @@ -2,6 +2,7 @@ use amqprs::{ channel::{BasicConsumeArguments, Channel, QueueBindArguments, QueueDeclareArguments}, connection::{Connection, OpenConnectionArguments}, consumer::AsyncConsumer, + FieldTable, }; use revolt_config::{config, Settings}; use tokio::sync::Notify; @@ -9,12 +10,11 @@ use tokio::sync::Notify; mod consumers; use consumers::{ inbound::{ - fr_accepted::FRAcceptedConsumer, fr_received::FRReceivedConsumer, generic::GenericConsumer, - message::MessageConsumer, + ack::AckConsumer, fr_accepted::FRAcceptedConsumer, fr_received::FRReceivedConsumer, + generic::GenericConsumer, message::MessageConsumer, }, outbound::{apn::ApnsOutboundConsumer, fcm::FcmOutboundConsumer, vapid::VapidOutboundConsumer}, }; -use log::info; #[tokio::main(flavor = "multi_thread", worker_threads = 2)] async fn main() { @@ -51,6 +51,7 @@ async fn main() { &config, &config.pushd.generic_queue, config.pushd.get_generic_routing_key().as_str(), + None, GenericConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -62,6 +63,7 @@ async fn main() { &config, &config.pushd.message_queue, config.pushd.get_message_routing_key().as_str(), + None, MessageConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -73,6 +75,7 @@ async fn main() { &config, &config.pushd.fr_received_queue, config.pushd.get_fr_received_routing_key().as_str(), + None, FRReceivedConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -84,6 +87,7 @@ async fn main() { &config, &config.pushd.fr_accepted_queue, config.pushd.get_fr_accepted_routing_key().as_str(), + None, FRAcceptedConsumer::new(db.clone(), authifier.clone()), ) .await, @@ -95,10 +99,25 @@ async fn main() { &config, &config.pushd.apn.queue, &config.pushd.apn.queue, + None, ApnsOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, ); + + let mut table = FieldTable::new(); + table.insert("x-message-deduplication".try_into().unwrap(), "true".into()); + + connections.push( + make_queue_and_consume( + &config, + &config.pushd.apn.queue, + &config.pushd.apn.queue, + Some(table), + AckConsumer::new(db.clone(), authifier.clone()), + ) + .await, + ); } if !config.pushd.fcm.auth_uri.is_empty() { @@ -107,6 +126,7 @@ async fn main() { &config, &config.pushd.fcm.queue, &config.pushd.fcm.queue, + None, FcmOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, @@ -119,6 +139,7 @@ async fn main() { &config, &config.pushd.vapid.queue, &config.pushd.vapid.queue, + None, VapidOutboundConsumer::new(db.clone()).await.unwrap(), ) .await, @@ -138,6 +159,7 @@ async fn make_queue_and_consume( config: &Settings, queue_name: &str, routing_key: &str, + queue_args: Option, consumer: F, ) -> (Channel, Connection) where @@ -164,9 +186,14 @@ where let queue_name = queue_name.as_str(); - let args = QueueDeclareArguments::new(queue_name) - .durable(true) - .finish(); + let mut args = QueueDeclareArguments::new(queue_name); + args.durable(true); + + if let Some(arg) = queue_args { + args.arguments(arg); + } + + let args = args.finish(); _ = channel.queue_declare(args).await.unwrap().unwrap(); channel @@ -183,9 +210,10 @@ where .finish(); channel.basic_consume(consumer, args).await.unwrap(); - info!( + log::info!( "Consuming routing key {} as queue {}", - routing_key, queue_name + routing_key, + queue_name ); (channel, connection) } diff --git a/crates/delta/src/routes/channels/group_add_member.rs b/crates/delta/src/routes/channels/group_add_member.rs index 16ee84c73..79d9e6335 100644 --- a/crates/delta/src/routes/channels/group_add_member.rs +++ b/crates/delta/src/routes/channels/group_add_member.rs @@ -1,6 +1,6 @@ use revolt_database::{ util::{permissions::DatabasePermissionQuery, reference::Reference}, - Channel, Database, User, + Channel, Database, User, AMQP, }; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; use revolt_result::{create_error, Result}; @@ -15,6 +15,7 @@ use rocket_empty::EmptyResponse; #[put("//recipients/")] pub async fn add_member( db: &State, + amqp: &State, user: User, group_id: Reference, member_id: Reference, @@ -38,7 +39,7 @@ pub async fn add_member( } channel - .add_user_to_group(db, &member, &user.id) + .add_user_to_group(db, amqp, &member, &user.id) .await .map(|_| EmptyResponse) } diff --git a/crates/delta/src/routes/channels/group_remove_member.rs b/crates/delta/src/routes/channels/group_remove_member.rs index 3a6f00ef9..90cccfefa 100644 --- a/crates/delta/src/routes/channels/group_remove_member.rs +++ b/crates/delta/src/routes/channels/group_remove_member.rs @@ -1,4 +1,4 @@ -use revolt_database::{util::reference::Reference, Channel, Database, User}; +use revolt_database::{util::reference::Reference, Channel, Database, User, AMQP}; use revolt_permissions::ChannelPermission; use revolt_result::{create_error, Result}; @@ -12,6 +12,7 @@ use rocket_empty::EmptyResponse; #[delete("//recipients/")] pub async fn remove_member( db: &State, + amqp: &State, user: User, target: Reference, member: Reference, @@ -42,7 +43,7 @@ pub async fn remove_member( } channel - .remove_user_from_group(db, &member, Some(&user.id), false) + .remove_user_from_group(db, amqp, &member, Some(&user.id), false) .await .map(|_| EmptyResponse) } diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index 93a290579..0f7d81b03 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -3,7 +3,7 @@ use revolt_database::util::permissions::DatabasePermissionQuery; use revolt_database::{ util::idempotency::IdempotencyKey, util::reference::Reference, Database, User, }; -use revolt_database::{Interactions, Message}; +use revolt_database::{Interactions, Message, AMQP}; use revolt_models::v0; use revolt_permissions::PermissionQuery; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; @@ -19,6 +19,7 @@ use validator::Validate; #[post("//messages", data = "")] pub async fn message_send( db: &State, + amqp: &State, user: User, target: Reference, data: Json, @@ -93,6 +94,7 @@ pub async fn message_send( Ok(Json( Message::create_from_api( db, + amqp, channel, data, v0::MessageAuthor::User(&author), From 25f8f13a4a3810e5b90114d9108e865dc649a39b Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 21 Sep 2024 16:43:23 -0700 Subject: [PATCH 14/44] fix: lockfile --- Cargo.lock | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0117ec0ad..2ed565e4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5577,7 +5577,7 @@ dependencies = [ "async-trait", "authifier", "axum", - "base64 0.21.3", + "base64 0.21.7", "bson", "deadqueue", "decancer", @@ -7827,7 +7827,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "regex", - "syn 2.0.76", + "syn 2.0.77", "ulid 1.1.3", ] @@ -8114,16 +8114,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webp" version = "0.3.0" From 2ccb408db26794dd3134e90045c896760b01939d Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 22 Sep 2024 19:31:29 -0700 Subject: [PATCH 15/44] feat: update rocket to 5.0.1 --- Cargo.lock | 4 +- .../database/src/models/channels/model.rs | 6 +- .../database/src/models/messages/model.rs | 7 +- crates/delta/Cargo.toml | 6 +- crates/delta/src/main.rs | 6 +- crates/delta/src/routes/bots/invite.rs | 14 ++- .../src/routes/channels/channel_delete.rs | 11 +- .../delta/src/routes/channels/channel_edit.rs | 43 ++++++- .../delta/src/routes/channels/message_pin.rs | 100 ++++++++++------ .../delta/src/routes/channels/message_send.rs | 2 +- .../src/routes/channels/message_unpin.rs | 108 +++++++++++------- .../delta/src/routes/invites/invite_join.rs | 5 +- .../src/routes/webhooks/webhook_execute.rs | 4 +- .../routes/webhooks/webhook_execute_github.rs | 13 ++- crates/delta/src/util/ratelimiter.rs | 10 +- crates/delta/src/util/test.rs | 16 +-- 16 files changed, 230 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ed565e4d..bab6ee413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6029,8 +6029,8 @@ dependencies = [ [[package]] name = "rocket_cors" -version = "0.6.0-alpha1" -source = "git+https://github.com/lawliet89/rocket_cors?rev=c17e8145baa4790319fdb6a473e465b960f55e7c#c17e8145baa4790319fdb6a473e465b960f55e7c" +version = "0.6.0" +source = "git+https://github.com/lawliet89/rocket_cors?rev=072d90359b23e9b291df6b672c07c93de9c46011#072d90359b23e9b291df6b672c07c93de9c46011" dependencies = [ "http 0.2.12", "log", diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index 130952c97..26815ad5c 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -374,7 +374,7 @@ impl Channel { .into_message(id.to_string()) .send( db, - amqp, + Some(amqp), MessageAuthor::System { username: &user.username, avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), @@ -689,7 +689,7 @@ impl Channel { .into_message(id.to_string()) .send( db, - amqp, + Some(amqp), MessageAuthor::System { username: name, avatar: None, @@ -729,7 +729,7 @@ impl Channel { .into_message(id.to_string()) .send( db, - amqp, + Some(amqp), MessageAuthor::System { username: &user.username, avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index fa9a12b88..8756fa4ce 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -230,7 +230,7 @@ impl Message { #[allow(clippy::too_many_arguments)] pub async fn create_from_api( db: &Database, - amqp: &AMQP, + amqp: Option<&AMQP>, channel: Channel, data: DataMessageSend, author: MessageAuthor<'_>, @@ -453,7 +453,7 @@ impl Message { pub async fn send( &mut self, db: &Database, - amqp: &AMQP, + amqp: Option<&AMQP>, // this is optional mostly for tests. author: MessageAuthor<'_>, user: Option, member: Option, @@ -469,9 +469,10 @@ impl Message { ) .await?; - if !self.has_suppressed_notifications() { + if !self.has_suppressed_notifications() && amqp.is_some() { // send Push notifications if let Err(resp) = amqp + .unwrap() .message_sent( { match channel { diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index 47ec05add..f07d08ab0 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -53,10 +53,8 @@ async-std = { version = "1.8.0", features = [ lettre = "0.10.0-alpha.4" # web -rocket = { version = "0.5.0-rc.2", default-features = false, features = [ - "json", -] } -rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" } +rocket = { version = "0.5.1", default-features = false, features = ["json"] } +rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" } rocket_empty = { version = "0.1.1", features = ["schema"] } rocket_authifier = { version = "1.0.8" } rocket_prometheus = "0.10.0-rc.3" diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 898a1d1d9..5f737e4a4 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -55,9 +55,6 @@ pub async fn web() -> Rocket { } }); - // Launch background task workers - revolt_database::tasks::start_workers(db.clone(), authifier.database.clone()); - // Configure CORS let cors = CorsOptions { allowed_origins: AllowedOrigins::All, @@ -94,6 +91,9 @@ pub async fn web() -> Rocket { let amqp = AMQP::new(connection, channel); + // Launch background task workers + revolt_database::tasks::start_workers(db.clone(), amqp.clone()); + // Configure Rocket let rocket = rocket::build(); let prometheus = PrometheusMetrics::new(); diff --git a/crates/delta/src/routes/bots/invite.rs b/crates/delta/src/routes/bots/invite.rs index 120c36dab..10c09eed1 100644 --- a/crates/delta/src/routes/bots/invite.rs +++ b/crates/delta/src/routes/bots/invite.rs @@ -1,6 +1,6 @@ use revolt_database::util::permissions::DatabasePermissionQuery; -use revolt_database::Member; use revolt_database::{util::reference::Reference, Database, User}; +use revolt_database::{Member, AMQP}; use revolt_models::v0; use revolt_permissions::{ calculate_channel_permissions, calculate_server_permissions, ChannelPermission, @@ -18,6 +18,7 @@ use rocket_empty::EmptyResponse; #[post("//invite", data = "")] pub async fn invite_bot( db: &State, + amqp: &State, user: User, target: Reference, dest: Json, @@ -55,7 +56,7 @@ pub async fn invite_bot( .throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?; channel - .add_user_to_group(db, &bot_user, &user.id) + .add_user_to_group(db, amqp, &bot_user, &user.id) .await .map(|_| EmptyResponse) } @@ -93,9 +94,12 @@ mod test { .client .post(format!("/bots/{}/invite", bot.id)) .header(ContentType::JSON) - .body(json!(v0::InviteBotDestination::Group { - group: group.id().to_string() - }).to_string()) + .body( + json!(v0::InviteBotDestination::Group { + group: group.id().to_string() + }) + .to_string(), + ) .header(Header::new("x-session-token", session.token.to_string())) .dispatch() .await; diff --git a/crates/delta/src/routes/channels/channel_delete.rs b/crates/delta/src/routes/channels/channel_delete.rs index ae09ee70f..86dea166c 100644 --- a/crates/delta/src/routes/channels/channel_delete.rs +++ b/crates/delta/src/routes/channels/channel_delete.rs @@ -1,6 +1,6 @@ use revolt_database::{ util::{permissions::DatabasePermissionQuery, reference::Reference}, - Channel, Database, PartialChannel, User, + Channel, Database, PartialChannel, User, AMQP, }; use revolt_models::v0; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; @@ -15,6 +15,7 @@ use rocket_empty::EmptyResponse; #[delete("/?")] pub async fn delete( db: &State, + amqp: &State, user: User, target: Reference, options: v0::OptionsChannelDelete, @@ -39,7 +40,13 @@ pub async fn delete( .await .map(|_| EmptyResponse), Channel::Group { .. } => channel - .remove_user_from_group(db, &user, None, options.leave_silently.unwrap_or_default()) + .remove_user_from_group( + db, + amqp, + &user, + None, + options.leave_silently.unwrap_or_default(), + ) .await .map(|_| EmptyResponse), Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => { diff --git a/crates/delta/src/routes/channels/channel_edit.rs b/crates/delta/src/routes/channels/channel_edit.rs index a26a7b344..f4bb48ed9 100644 --- a/crates/delta/src/routes/channels/channel_edit.rs +++ b/crates/delta/src/routes/channels/channel_edit.rs @@ -1,6 +1,6 @@ use revolt_database::{ util::{permissions::DatabasePermissionQuery, reference::Reference}, - Channel, Database, File, PartialChannel, SystemMessage, User, + Channel, Database, File, PartialChannel, SystemMessage, User, AMQP, }; use revolt_models::v0; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; @@ -15,6 +15,7 @@ use validator::Validate; #[patch("/", data = "")] pub async fn edit( db: &State, + amqp: &State, user: User, target: Reference, data: Json, @@ -73,7 +74,15 @@ pub async fn edit( return Err(create_error!(InvalidOperation)); } .into_message(channel.id().to_string()) - .send(db, user.as_author_for_system(), None, None, &channel, false) + .send( + db, + Some(amqp), + user.as_author_for_system(), + None, + None, + &channel, + false, + ) .await .ok(); } @@ -151,7 +160,15 @@ pub async fn edit( by: user.id.clone(), } .into_message(channel.id().to_string()) - .send(db, user.as_author_for_system(), None, None, &channel, false) + .send( + db, + Some(amqp), + user.as_author_for_system(), + None, + None, + &channel, + false, + ) .await .ok(); } @@ -161,7 +178,15 @@ pub async fn edit( by: user.id.clone(), } .into_message(channel.id().to_string()) - .send(db, user.as_author_for_system(), None, None, &channel, false) + .send( + db, + Some(amqp), + user.as_author_for_system(), + None, + None, + &channel, + false, + ) .await .ok(); } @@ -171,7 +196,15 @@ pub async fn edit( by: user.id.clone(), } .into_message(channel.id().to_string()) - .send(db, user.as_author_for_system(), None, None, &channel, false) + .send( + db, + Some(amqp), + user.as_author_for_system(), + None, + None, + &channel, + false, + ) .await .ok(); } diff --git a/crates/delta/src/routes/channels/message_pin.rs b/crates/delta/src/routes/channels/message_pin.rs index 5dc318f87..d8df7febd 100644 --- a/crates/delta/src/routes/channels/message_pin.rs +++ b/crates/delta/src/routes/channels/message_pin.rs @@ -1,5 +1,6 @@ use revolt_database::{ - util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, PartialMessage, SystemMessage, User + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Database, PartialMessage, SystemMessage, User, AMQP, }; use revolt_models::v0::MessageAuthor; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; @@ -14,6 +15,7 @@ use rocket_empty::EmptyResponse; #[post("//messages//pin")] pub async fn message_pin( db: &State, + amqp: &State, user: User, target: Reference, msg: Reference, @@ -28,30 +30,38 @@ pub async fn message_pin( let mut message = msg.as_message_in_channel(db, channel.id()).await?; if message.pinned.unwrap_or_default() { - return Err(create_error!(AlreadyPinned)) + return Err(create_error!(AlreadyPinned)); } - message.update(db, PartialMessage { - pinned: Some(true), - ..Default::default() - }, vec![]).await?; + message + .update( + db, + PartialMessage { + pinned: Some(true), + ..Default::default() + }, + vec![], + ) + .await?; SystemMessage::MessagePinned { id: message.id.clone(), - by: user.id.clone() + by: user.id.clone(), } .into_message(channel.id().to_string()) .send( db, + Some(amqp), MessageAuthor::System { username: &user.username, - avatar: user.avatar.as_ref().map(|file| file.id.as_ref()) + avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), }, None, None, &channel, - false - ).await?; + false, + ) + .await?; Ok(EmptyResponse) } @@ -59,7 +69,11 @@ pub async fn message_pin( #[cfg(test)] mod test { use crate::{rocket, util::test::TestHarness}; - use revolt_database::{events::client::EventV1, util::{idempotency::IdempotencyKey, reference::Reference}, Member, Message, Server}; + use revolt_database::{ + events::client::EventV1, + util::{idempotency::IdempotencyKey, reference::Reference}, + Member, Message, Server, + }; use revolt_models::v0::{self, SystemMessage}; use rocket::http::{Header, Status}; @@ -75,24 +89,29 @@ mod test { ..Default::default() }, &user, - true - ).await.expect("Failed to create test server"); + true, + ) + .await + .expect("Failed to create test server"); - let (member, channels) = Member::create(&harness.db, &server, &user, Some(channels)).await.expect("Failed to create member"); + let (member, channels) = Member::create(&harness.db, &server, &user, Some(channels)) + .await + .expect("Failed to create member"); let channel = &channels[0]; let message = Message::create_from_api( &harness.db, + None, channel.clone(), v0::DataMessageSend { - content:Some("Test message".to_string()), + content: Some("Test message".to_string()), nonce: None, attachments: None, replies: None, embeds: None, masquerade: None, interactions: None, - flags: None + flags: None, }, v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), Some(user.clone().into(&harness.db, Some(&user)).await), @@ -100,14 +119,18 @@ mod test { user.limits().await, IdempotencyKey::unchecked_from_string("0".to_string()), false, - false + false, ) .await .expect("Failed to create message"); let response = harness .client - .post(format!("/channels/{}/messages/{}/pin", channel.id(), &message.id)) + .post(format!( + "/channels/{}/messages/{}/pin", + channel.id(), + &message.id + )) .header(Header::new("x-session-token", session.token.to_string())) .dispatch() .await; @@ -115,34 +138,37 @@ mod test { assert_eq!(response.status(), Status::NoContent); drop(response); - harness.wait_for_event(channel.id(), |event| { - match event { - EventV1::Message(message) => { - match &message.system { - Some(SystemMessage::MessagePinned { by, .. }) => { - assert_eq!(by, &user.id); + harness + .wait_for_event(channel.id(), |event| match event { + EventV1::Message(message) => match &message.system { + Some(SystemMessage::MessagePinned { by, .. }) => { + assert_eq!(by, &user.id); - true - }, - _ => false + true } + _ => false, }, - _ => false - } - }).await; + _ => false, + }) + .await; - harness.wait_for_event(channel.id(), |event| { - match event { - EventV1::MessageUpdate { id, channel: channel_id, data, .. } => { + harness + .wait_for_event(channel.id(), |event| match event { + EventV1::MessageUpdate { + id, + channel: channel_id, + data, + .. + } => { assert_eq!(id, &message.id); assert_eq!(channel_id, channel.id()); assert_eq!(data.pinned, Some(true)); true - }, - _ => false - } - }).await; + } + _ => false, + }) + .await; let updated_message = Reference::from_unchecked(message.id) .as_message(&harness.db) diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index 0f7d81b03..ab3ba5582 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -94,7 +94,7 @@ pub async fn message_send( Ok(Json( Message::create_from_api( db, - amqp, + Some(amqp), channel, data, v0::MessageAuthor::User(&author), diff --git a/crates/delta/src/routes/channels/message_unpin.rs b/crates/delta/src/routes/channels/message_unpin.rs index 4084fc119..67154842d 100644 --- a/crates/delta/src/routes/channels/message_unpin.rs +++ b/crates/delta/src/routes/channels/message_unpin.rs @@ -1,5 +1,6 @@ use revolt_database::{ - util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, FieldsMessage, PartialMessage, SystemMessage, User + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Database, FieldsMessage, PartialMessage, SystemMessage, User, AMQP, }; use revolt_models::v0::MessageAuthor; use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; @@ -14,6 +15,7 @@ use rocket_empty::EmptyResponse; #[delete("//messages//pin")] pub async fn message_unpin( db: &State, + amqp: &State, user: User, target: Reference, msg: Reference, @@ -28,27 +30,31 @@ pub async fn message_unpin( let mut message = msg.as_message_in_channel(db, channel.id()).await?; if !message.pinned.unwrap_or_default() { - return Err(create_error!(NotPinned)) + return Err(create_error!(NotPinned)); } - message.update(db, PartialMessage::default(), vec![FieldsMessage::Pinned]).await?; + message + .update(db, PartialMessage::default(), vec![FieldsMessage::Pinned]) + .await?; SystemMessage::MessageUnpinned { id: message.id.clone(), - by: user.id.clone() + by: user.id.clone(), } .into_message(channel.id().to_string()) .send( db, + Some(amqp), MessageAuthor::System { username: &user.username, - avatar: user.avatar.as_ref().map(|file| file.id.as_ref()) + avatar: user.avatar.as_ref().map(|file| file.id.as_ref()), }, None, None, &channel, - false - ).await?; + false, + ) + .await?; Ok(EmptyResponse) } @@ -56,7 +62,11 @@ pub async fn message_unpin( #[cfg(test)] mod test { use crate::{rocket, util::test::TestHarness}; - use revolt_database::{events::client::EventV1, util::{idempotency::IdempotencyKey, reference::Reference}, Member, Message, PartialMessage, Server}; + use revolt_database::{ + events::client::EventV1, + util::{idempotency::IdempotencyKey, reference::Reference}, + Member, Message, PartialMessage, Server, + }; use revolt_models::v0::{self, FieldsMessage, SystemMessage}; use rocket::http::{Header, Status}; @@ -72,26 +82,34 @@ mod test { ..Default::default() }, &user, - true - ).await.expect("Failed to create test server"); + true, + ) + .await + .expect("Failed to create test server"); let channel = &channels[0]; - Member::create(&harness.db, &server, &user, Some(channels.clone())).await.expect("Failed to create member"); - let member = Reference::from_unchecked(user.id.clone()).as_member(&harness.db, &server.id).await.expect("Failed to get member"); + Member::create(&harness.db, &server, &user, Some(channels.clone())) + .await + .expect("Failed to create member"); + let member = Reference::from_unchecked(user.id.clone()) + .as_member(&harness.db, &server.id) + .await + .expect("Failed to get member"); let message = Message::create_from_api( &harness.db, + None, channel.clone(), v0::DataMessageSend { - content:Some("Test message".to_string()), + content: Some("Test message".to_string()), nonce: None, attachments: None, replies: None, embeds: None, masquerade: None, interactions: None, - flags: None + flags: None, }, v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), Some(user.clone().into(&harness.db, Some(&user)).await), @@ -99,23 +117,31 @@ mod test { user.limits().await, IdempotencyKey::unchecked_from_string("0".to_string()), false, - false + false, ) .await .expect("Failed to create message"); - harness.db.update_message( - &message.id, - &PartialMessage { - pinned: Some(true), - ..Default::default() - }, - vec![] - ).await.expect("Failed to update message"); + harness + .db + .update_message( + &message.id, + &PartialMessage { + pinned: Some(true), + ..Default::default() + }, + vec![], + ) + .await + .expect("Failed to update message"); let response = harness .client - .delete(format!("/channels/{}/messages/{}/pin", channel.id(), &message.id)) + .delete(format!( + "/channels/{}/messages/{}/pin", + channel.id(), + &message.id + )) .header(Header::new("x-session-token", session.token.to_string())) .dispatch() .await; @@ -123,33 +149,31 @@ mod test { assert_eq!(response.status(), Status::NoContent); drop(response); - harness.wait_for_event(channel.id(), |event| { - match event { - EventV1::Message(message) => { - match &message.system { - Some(SystemMessage::MessageUnpinned { by, .. }) => { - assert_eq!(by, &user.id); + harness + .wait_for_event(channel.id(), |event| match event { + EventV1::Message(message) => match &message.system { + Some(SystemMessage::MessageUnpinned { by, .. }) => { + assert_eq!(by, &user.id); - true - }, - _ => false + true } + _ => false, }, - _ => false - } - }).await; + _ => false, + }) + .await; - harness.wait_for_event(channel.id(), |event| { - match event { + harness + .wait_for_event(channel.id(), |event| match event { EventV1::MessageUpdate { id, clear, .. } => { assert_eq!(&message.id, id); assert_eq!(clear, &[FieldsMessage::Pinned]); true - }, - _ => false - } - }).await; + } + _ => false, + }) + .await; let updated_message = Reference::from_unchecked(message.id) .as_message(&harness.db) diff --git a/crates/delta/src/routes/invites/invite_join.rs b/crates/delta/src/routes/invites/invite_join.rs index 2fa872d3f..83b591951 100644 --- a/crates/delta/src/routes/invites/invite_join.rs +++ b/crates/delta/src/routes/invites/invite_join.rs @@ -1,4 +1,4 @@ -use revolt_database::{util::reference::Reference, Channel, Database, Invite, Member, User}; +use revolt_database::{util::reference::Reference, Channel, Database, Invite, Member, User, AMQP}; use revolt_models::v0::{self, InviteJoinResponse}; use revolt_result::{create_error, Result}; use rocket::{serde::json::Json, State}; @@ -10,6 +10,7 @@ use rocket::{serde::json::Json, State}; #[post("/")] pub async fn join( db: &State, + amqp: &State, user: User, target: Reference, ) -> Result> { @@ -34,7 +35,7 @@ pub async fn join( channel, creator, .. } => { let mut channel = db.fetch_channel(channel).await?; - channel.add_user_to_group(db, &user, creator).await?; + channel.add_user_to_group(db, amqp, &user, creator).await?; if let Channel::Group { recipients, .. } = &channel { Ok(Json(InviteJoinResponse::Group { users: User::fetch_many_ids_as_mutuals(db, &user, recipients).await?, diff --git a/crates/delta/src/routes/webhooks/webhook_execute.rs b/crates/delta/src/routes/webhooks/webhook_execute.rs index 29b33dde3..e43ee9348 100644 --- a/crates/delta/src/routes/webhooks/webhook_execute.rs +++ b/crates/delta/src/routes/webhooks/webhook_execute.rs @@ -1,7 +1,7 @@ use revolt_config::config; use revolt_database::{ util::{idempotency::IdempotencyKey, reference::Reference}, - Database, Message, + Database, Message, AMQP, }; use revolt_models::v0; use revolt_permissions::{ChannelPermission, PermissionValue}; @@ -17,6 +17,7 @@ use validator::Validate; #[post("//", data = "")] pub async fn webhook_execute( db: &State, + amqp: &State, webhook_id: Reference, token: String, data: Json, @@ -56,6 +57,7 @@ pub async fn webhook_execute( Ok(Json( Message::create_from_api( db, + Some(amqp), channel, data, v0::MessageAuthor::Webhook(&webhook.into()), diff --git a/crates/delta/src/routes/webhooks/webhook_execute_github.rs b/crates/delta/src/routes/webhooks/webhook_execute_github.rs index 85bf2e429..a5b0d12b9 100644 --- a/crates/delta/src/routes/webhooks/webhook_execute_github.rs +++ b/crates/delta/src/routes/webhooks/webhook_execute_github.rs @@ -1,4 +1,4 @@ -use revolt_database::{util::reference::Reference, Database, Message}; +use revolt_database::{util::reference::Reference, Database, Message, AMQP}; use revolt_models::v0::{MessageAuthor, SendableEmbed, Webhook}; use revolt_result::{create_error, Error, Result}; use revolt_rocket_okapi::{ @@ -635,7 +635,10 @@ impl<'r> FromRequest<'r> for EventHeader<'r> { async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome { let headers = request.headers(); let Some(event) = headers.get_one("X-GitHub-Event") else { - return rocket::request::Outcome::Failure((Status::BadRequest, create_error!(InvalidOperation))) + return rocket::request::Outcome::Error(( + Status::BadRequest, + create_error!(InvalidOperation), + )); }; rocket::request::Outcome::Success(Self(event)) @@ -747,6 +750,7 @@ fn convert_event(data: &str, event_name: &str) -> Result { #[post("///github", data = "")] pub async fn webhook_execute_github( db: &State, + amqp: &State, webhook_id: Reference, token: String, event: EventHeader<'_>, @@ -784,7 +788,9 @@ pub async fn webhook_execute_github( r#ref, .. }) => { - let Some(branch) = r#ref.split('/').nth(2) else { return Ok(()) }; + let Some(branch) = r#ref.split('/').nth(2) else { + return Ok(()); + }; if forced { let description = format!( @@ -1074,6 +1080,7 @@ pub async fn webhook_execute_github( message .send( db, + Some(amqp), MessageAuthor::Webhook(&webhook.into()), None, None, diff --git a/crates/delta/src/util/ratelimiter.rs b/crates/delta/src/util/ratelimiter.rs index ce7317ffc..c95f818df 100644 --- a/crates/delta/src/util/ratelimiter.rs +++ b/crates/delta/src/util/ratelimiter.rs @@ -235,7 +235,7 @@ impl<'r> FromRequest<'r> for Ratelimiter { match ratelimiter { Ok(ratelimiter) => Outcome::Success(*ratelimiter), - Err(ratelimiter) => Outcome::Failure((Status::TooManyRequests, *ratelimiter)), + Err(ratelimiter) => Outcome::Error((Status::TooManyRequests, *ratelimiter)), } } } @@ -264,7 +264,7 @@ impl Fairing for RatelimitFairing { async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) { use rocket::outcome::Outcome; - if let Outcome::Failure(_) = request.guard::().await { + if let Outcome::Error(_) = request.guard::().await { info!( "User rate-limited on route {}! (IP = {:?})", request.uri(), @@ -278,7 +278,7 @@ impl Fairing for RatelimitFairing { async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { let guard = request.guard::().await; - let (Outcome::Success(ratelimiter) | Outcome::Failure((_, ratelimiter))) = guard else { + let (Outcome::Success(ratelimiter) | Outcome::Error((_, ratelimiter))) = guard else { unreachable!() }; let Ratelimiter { @@ -293,7 +293,7 @@ impl Fairing for RatelimitFairing { response.set_raw_header("X-RateLimit-Remaining", remaining.to_string()); response.set_raw_header("X-RateLimit-Reset-After", reset.to_string()); - if guard.is_failure() { + if guard.is_error() { response.set_status(Status::TooManyRequests); } } @@ -313,7 +313,7 @@ impl<'r> FromRequest<'r> for RatelimitInformation { async fn from_request(request: &'r rocket::Request<'_>) -> Outcome { let info = match request.guard::().await { Outcome::Success(ratelimiter) => RatelimitInformation::Success(ratelimiter), - Outcome::Failure((_, ratelimiter)) => RatelimitInformation::Failure { + Outcome::Error((_, ratelimiter)) => RatelimitInformation::Failure { retry_after: ratelimiter.reset, }, _ => unreachable!(), diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 467e945be..f686c0183 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -100,15 +100,17 @@ impl TestHarness { } let mut stream = self.sub.on_message(); - while let Some(item) = stream.next().await { - let msg_topic = item.get_channel_name(); - let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); + while let Some(underlying) = stream.next().await { + if let Ok(item) = underlying { + let msg_topic = item.get_channel_name(); + let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); - if topic == msg_topic && predicate(&payload) { - return payload; - } + if topic == msg_topic && predicate(&payload) { + return payload; + } - self.event_buffer.push((msg_topic.to_string(), payload)); + self.event_buffer.push((msg_topic.to_string(), payload)); + } } // WARNING: if predicate is never satisfied, this will never return From bc641c3c902d47f689b9aa9f17f8e7c5f21aee77 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 22 Sep 2024 23:00:32 -0700 Subject: [PATCH 16/44] fix: fix queues and ack bugs --- crates/core/config/src/lib.rs | 4 ++ crates/core/database/src/amqp/amqp.rs | 65 +++++++++++++++---- crates/core/database/src/tasks/ack.rs | 27 ++++---- .../pushd/src/consumers/inbound/ack.rs | 16 ++++- .../pushd/src/consumers/outbound/apn.rs | 2 + crates/daemons/pushd/src/main.rs | 4 +- 6 files changed, 88 insertions(+), 30 deletions(-) diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 6474c8d5e..7d9c2414a 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -189,6 +189,10 @@ impl Pushd { } } + pub fn get_ack_routing_key(&self) -> String { + self.get_routing_key(self.ack_queue.clone()) + } + pub fn get_message_routing_key(&self) -> String { self.get_routing_key(self.message_queue.clone()) } diff --git a/crates/core/database/src/amqp/amqp.rs b/crates/core/database/src/amqp/amqp.rs index 25c763bd8..39b4820eb 100644 --- a/crates/core/database/src/amqp/amqp.rs +++ b/crates/core/database/src/amqp/amqp.rs @@ -3,8 +3,8 @@ use std::collections::HashSet; use crate::events::rabbit::*; use crate::User; use amqprs::channel::BasicPublishArguments; -use amqprs::BasicProperties; use amqprs::{channel::Channel, connection::Connection, error::Error as AMQPError}; +use amqprs::{BasicProperties, FieldTable}; use revolt_models::v0::PushNotification; use revolt_presence::filter_online; @@ -37,8 +37,11 @@ impl AMQP { }; let payload = to_string(&payload).unwrap(); - info!("Sending friend request accepted event: {}", &payload); - + debug!( + "Sending friend request accept payload on channel {}: {}", + config.pushd.get_fr_accepted_routing_key(), + payload + ); self.channel .basic_publish( BasicProperties::default() @@ -46,7 +49,10 @@ impl AMQP { .with_persistence(true) .finish(), payload.into(), - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.fr_accepted_queue), + BasicPublishArguments::new( + &config.pushd.exchange, + &config.pushd.get_fr_accepted_routing_key(), + ), ) .await } @@ -63,7 +69,11 @@ impl AMQP { }; let payload = to_string(&payload).unwrap(); - info!("Sending friend request received event: {}", &payload); + debug!( + "Sending friend request received payload on channel {}: {}", + config.pushd.get_fr_received_routing_key(), + payload + ); self.channel .basic_publish( @@ -72,7 +82,10 @@ impl AMQP { .with_persistence(true) .finish(), payload.into(), - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.fr_received_queue), + BasicPublishArguments::new( + &config.pushd.exchange, + &config.pushd.get_fr_received_routing_key(), + ), ) .await } @@ -93,6 +106,12 @@ impl AMQP { }; let payload = to_string(&payload).unwrap(); + debug!( + "Sending generic payload on channel {}: {}", + config.pushd.get_generic_routing_key(), + payload + ); + self.channel .basic_publish( BasicProperties::default() @@ -100,7 +119,10 @@ impl AMQP { .with_persistence(true) .finish(), payload.into(), - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.generic_queue), + BasicPublishArguments::new( + &config.pushd.exchange, + &config.pushd.get_generic_routing_key(), + ), ) .await } @@ -127,6 +149,12 @@ impl AMQP { }; let payload = to_string(&payload).unwrap(); + debug!( + "Sending message payload on channel {}: {}", + config.pushd.get_message_routing_key(), + payload + ); + self.channel .basic_publish( BasicProperties::default() @@ -134,7 +162,10 @@ impl AMQP { .with_persistence(true) .finish(), payload.into(), - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue), + BasicPublishArguments::new( + &config.pushd.exchange, + &config.pushd.get_message_routing_key(), + ), ) .await } @@ -148,20 +179,32 @@ impl AMQP { let config = revolt_config::config().await; let payload = AckPayload { - user_id, - channel_id, + user_id: user_id.clone(), + channel_id: channel_id.clone(), message_id, }; let payload = to_string(&payload).unwrap(); + info!( + "Sending ack payload on channel {}: {}", + config.pushd.ack_queue, payload + ); + + let mut headers = FieldTable::new(); + headers.insert( + "x-deduplication-header".try_into().unwrap(), + format!("{}-{}", &user_id, &channel_id).into(), + ); + self.channel .basic_publish( BasicProperties::default() .with_content_type("application/json") .with_persistence(true) + //.with_headers(headers) .finish(), payload.into(), - BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.message_queue), + BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.ack_queue), ) .await } diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index d50c2c55b..e7f835b31 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -123,24 +123,21 @@ pub async fn worker(db: Database, amqp: AMQP) { mut event, }) = Q.try_pop() { - let key = (user, channel); - if let Some(task) = tasks.get_mut(&key) { - task.delay(); - - match &mut event { - AckEvent::AddMention { ids } => { - if let AckEvent::AddMention { ids: existing } = &mut task.data.event { - existing.append(ids); - } else { - task.data.event = event; - } - } - AckEvent::AckMessage { .. } => { + match &mut event { + // this is a bit of a test, we're no longer delaying/batching AddMentions in an effort for + // pushd to have the mention in the db when it sends a message notification. + AckEvent::AddMention { .. } => { + handle_ack_event(&event, &db, &amqp, &user, &channel).await; + } + AckEvent::AckMessage { .. } => { + let key = (user, channel); + if let Some(task) = tasks.get_mut(&key) { + task.delay(); task.data.event = event; + } else { + tasks.insert(key, DelayedTask::new(Task { event })); } } - } else { - tasks.insert(key, DelayedTask::new(Task { event })); } } diff --git a/crates/daemons/pushd/src/consumers/inbound/ack.rs b/crates/daemons/pushd/src/consumers/inbound/ack.rs index a44de4221..2a46ebf8e 100644 --- a/crates/daemons/pushd/src/consumers/inbound/ack.rs +++ b/crates/daemons/pushd/src/consumers/inbound/ack.rs @@ -72,6 +72,7 @@ impl AsyncConsumer for AckConsumer { // Step 1: fetch unreads and don't continue if there's no unreads #[allow(clippy::disallowed_methods)] let unreads = self.db.fetch_unread_mentions(&payload.user_id).await; + println!("unreads: {:?}", unreads); if let Ok(u) = &unreads { if u.is_empty() { @@ -100,12 +101,16 @@ impl AsyncConsumer for AckConsumer { return; } + println!("sessions: {:?}", apple_sessions); + // Step 3: calculate the actual mention count, since we have to send it out let mut mention_count = 0; for u in &unreads.unwrap() { mention_count += u.mentions.as_ref().unwrap().len() } + println!("mention count: {}", mention_count); + // Step 4: loop through each apple session and send the badge update for session in apple_sessions { let service_payload = PayloadToService { @@ -124,12 +129,19 @@ impl AsyncConsumer for AckConsumer { ) .finish(); + println!( + "Publishing ack to apn session {}", + session.subscription.as_ref().unwrap().auth + ); + publish_message(self, p.into(), args).await; } else { - log::error!("Failed to serialize ack badge update payload!"); - log::error!("{:?}", raw_service_payload.unwrap_err()) + println!("Failed to serialize ack badge update payload!"); + println!("{:?}", raw_service_payload.unwrap_err()) } } + + println!("Done!"); } } } diff --git a/crates/daemons/pushd/src/consumers/outbound/apn.rs b/crates/daemons/pushd/src/consumers/outbound/apn.rs index a3e5c8d93..923ec8af3 100644 --- a/crates/daemons/pushd/src/consumers/outbound/apn.rs +++ b/crates/daemons/pushd/src/consumers/outbound/apn.rs @@ -81,6 +81,8 @@ impl ApnsOutboundConsumer { } } + println!("Got badge count for APN: {}", mention_count); + return Some(mention_count); } None diff --git a/crates/daemons/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs index 05ab72a1f..3c9da6dc1 100644 --- a/crates/daemons/pushd/src/main.rs +++ b/crates/daemons/pushd/src/main.rs @@ -111,8 +111,8 @@ async fn main() { connections.push( make_queue_and_consume( &config, - &config.pushd.apn.queue, - &config.pushd.apn.queue, + &config.pushd.ack_queue, + &config.pushd.ack_queue, Some(table), AckConsumer::new(db.clone(), authifier.clone()), ) From e3b0ebb10b0761ff5d76785680fb3a29b5f226fa Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 28 Sep 2024 00:09:20 -0700 Subject: [PATCH 17/44] Move rabbit messsage processing into ack queue --- crates/core/config/Revolt.toml | 5 + crates/core/config/src/lib.rs | 16 ++ .../database/src/models/channels/model.rs | 2 +- .../database/src/models/messages/model.rs | 53 +++--- .../src/models/server_members/model.rs | 4 +- crates/core/database/src/tasks/ack.rs | 160 +++++++++++++----- 6 files changed, 172 insertions(+), 68 deletions(-) diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index bb2e27031..b7fe1b51c 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -243,6 +243,11 @@ icons = 2_500_000 banners = 6_000_000 emojis = 500_000 +[features.advanced] +# The max amount of messages the rabbitmq provider/db mention adder job will delay for before forcing handling of a channel. +# default: 5 +process_message_delay_limit = 5 + [sentry] # Configuration for Sentry error reporting api = "" diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 7d9c2414a..eb2d2210b 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -278,10 +278,26 @@ pub struct FeaturesLimitsCollection { pub roles: HashMap, } +#[derive(Deserialize, Debug, Clone)] +pub struct FeaturesAdvanced { + #[serde(default)] + pub process_message_delay_limit: u16, +} + +impl Default for FeaturesAdvanced { + fn default() -> Self { + Self { + process_message_delay_limit: 5, + } + } +} + #[derive(Deserialize, Debug, Clone)] pub struct Features { pub limits: FeaturesLimitsCollection, pub webhooks_enabled: bool, + #[serde(default)] + pub advanced: FeaturesAdvanced, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index 26815ad5c..f981ff1c1 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -641,7 +641,7 @@ impl Channel { .private(user.to_string()) .await; - crate::tasks::ack::queue( + crate::tasks::ack::queue_ack( self.id().to_string(), user.to_string(), AckEvent::AckMessage { diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 8756fa4ce..b69c49998 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -408,6 +408,9 @@ impl Message { member: Option, is_dm: bool, generate_embeds: bool, + // This determines if this function should queue the mentions task or if somewhere else will. + // If this is true, you MUST call tasks::ack::queue yourself. + mentions_elsewhere: bool, ) -> Result<()> { db.insert_message(self).await?; @@ -420,13 +423,12 @@ impl Message { tasks::last_message_id::queue(self.channel.to_string(), self.id.to_string(), is_dm).await; // Add mentions for affected users - if let Some(mentions) = &self.mentions { - for user in mentions { - tasks::ack::queue( + if !mentions_elsewhere { + if let Some(mentions) = &self.mentions { + tasks::ack::queue_message( self.channel.to_string(), - user.to_string(), - AckEvent::AddMention { - ids: vec![self.id.to_string()], + AckEvent::ProcessMessage { + messages: vec![(None, self.clone(), mentions.clone(), true)], }, ) .await; @@ -466,15 +468,25 @@ impl Message { member.clone(), matches!(channel, Channel::DirectMessage { .. }), generate_embeds, + true, ) .await?; - if !self.has_suppressed_notifications() && amqp.is_some() { + if !self.has_suppressed_notifications() { // send Push notifications - if let Err(resp) = amqp - .unwrap() - .message_sent( - { + tasks::ack::queue_message( + self.channel.to_string(), + AckEvent::ProcessMessage { + messages: vec![( + Some( + PushNotification::from( + self.clone().into_model(user, member), + Some(author), + channel.to_owned().into(), + ) + .await, + ), + self.clone(), match channel { Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => recipients.clone(), @@ -482,19 +494,12 @@ impl Message { self.mentions.clone().unwrap_or_default() } _ => vec![], - } - }, - PushNotification::from( - self.clone().into_model(user, member), - Some(author), - channel.to_owned().into(), - ) - .await, - ) - .await - { - revolt_config::capture_error(&resp); - } + }, + true, + )], + }, + ) + .await; } Ok(()) diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs index 88f953cf8..72e161629 100644 --- a/crates/core/database/src/models/server_members/model.rs +++ b/crates/core/database/src/models/server_members/model.rs @@ -150,7 +150,7 @@ impl Member { id: user.id.clone(), } .into_message(id.to_string()) - .send_without_notifications(db, None, None, false, false) + .send_without_notifications(db, None, None, false, false, false) .await .ok(); } @@ -251,7 +251,7 @@ impl Member { } .into_message(id.to_string()) // TODO: support notifications here in the future? - .send_without_notifications(db, None, None, false, false) + .send_without_notifications(db, None, None, false, false, false) .await .ok(); } diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index e7f835b31..f92c3563d 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -1,24 +1,27 @@ // Queue Type: Debounced -use crate::{Database, AMQP}; +use crate::{Database, Message, AMQP}; use deadqueue::limited::Queue; use once_cell::sync::Lazy; -use std::{collections::HashMap, time::Duration}; +use revolt_models::v0::PushNotification; +use rocket::form::validate::Contains; +use std::{ + collections::{HashMap, HashSet}, + time::Duration, +}; +use validator::HasLen; use revolt_result::Result; -use super::{ - //apple_notifications::{self, ApnJob}, - DelayedTask, -}; +use super::DelayedTask; /// Enumeration of possible events #[derive(Debug, Eq, PartialEq)] pub enum AckEvent { - /// Add mentions for a user in a channel - AddMention { - /// Message IDs - ids: Vec, + /// Add mentions for a channel + ProcessMessage { + /// push notification, message, recipients, push silenced + messages: Vec<(Option, Message, Vec, bool)>, }, /// Acknowledge message in a channel for a user @@ -33,7 +36,7 @@ struct Data { /// Channel to ack channel: String, /// User to ack for - user: String, + user: Option, /// Event event: AckEvent, } @@ -46,10 +49,21 @@ struct Task { static Q: Lazy> = Lazy::new(|| Queue::new(10_000)); /// Queue a new task for a worker -pub async fn queue(channel: String, user: String, event: AckEvent) { +pub async fn queue_ack(channel: String, user: String, event: AckEvent) { Q.try_push(Data { channel, - user, + user: Some(user), + event, + }) + .ok(); + + info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); +} + +pub async fn queue_message(channel: String, event: AckEvent) { + Q.try_push(Data { + channel, + user: None, event, }) .ok(); @@ -61,25 +75,65 @@ pub async fn handle_ack_event( event: &AckEvent, db: &Database, amqp: &AMQP, - user: &str, + user: &Option, channel: &str, ) -> Result<()> { match &event { #[allow(clippy::disallowed_methods)] // event is sent by higher level function AckEvent::AckMessage { id } => { - if let Err(resp) = db.acknowledge_message(channel, user, id).await { - revolt_config::capture_error(&resp); - } + let _u = user.as_ref().unwrap(); + let user: &str = _u.as_str(); + + let unread = db.fetch_unread(user, channel).await?; + let updated = db.acknowledge_message(channel, user, id).await?; - if let Err(resp) = amqp - .ack_message(user.into(), channel.into(), id.into()) - .await - { - revolt_config::capture_error(&resp); + if let (Some(before), Some(after)) = (unread, updated) { + let before_mentions = before.mentions.unwrap_or_default().len(); + let after_mentions = after.mentions.unwrap_or_default().len(); + + let mentions_acked = before_mentions - after_mentions; + + if mentions_acked > 0 { + if let Err(err) = amqp + .ack_message(user.to_string(), channel.to_string(), id.to_owned()) + .await + { + revolt_config::capture_error(&err); + } + }; } } - AckEvent::AddMention { ids } => { - db.add_mention_to_unread(channel, user, ids).await?; + AckEvent::ProcessMessage { messages } => { + let mut users: HashSet<&String> = HashSet::new(); + + // find all the users we'll be notifying + messages + .iter() + .for_each(|(_, _, recipents, _)| users.extend(recipents.iter())); + + for user in users { + let message_ids: Vec = messages + .iter() + .filter(|(_, _, recipients, _)| recipients.contains(user)) + .map(|(_, message, _, _)| message.id.clone()) + .collect(); + + db.add_mention_to_unread(channel, user, &message_ids) + .await?; + } + + for (push, _, recipients, silenced) in messages { + if *silenced || push.is_none() { + continue; + } + + if let Err(err) = amqp + .message_sent(recipients.clone(), push.clone().unwrap()) + .await + { + revolt_config::capture_error(&err); + } + } } }; @@ -88,8 +142,8 @@ pub async fn handle_ack_event( /// Start a new worker pub async fn worker(db: Database, amqp: AMQP) { - let mut tasks = HashMap::<(String, String), DelayedTask>::new(); - let mut keys = vec![]; + let mut tasks = HashMap::<(Option, String, u8), DelayedTask>::new(); + let mut keys: Vec<(Option, String, u8)> = vec![]; loop { // Find due tasks. @@ -103,12 +157,12 @@ pub async fn worker(db: Database, amqp: AMQP) { for key in &keys { if let Some(task) = tasks.remove(key) { let Task { event } = task.data; - let (user, channel) = key; + let (user, channel, _) = key; if let Err(err) = handle_ack_event(&event, &db, &amqp, user, channel).await { - error!("{err:?} for {event:?}. ({user}, {channel})"); + error!("{err:?} for {event:?}. ({user:?}, {channel})"); } else { - info!("User {user} ack in {channel} with {event:?}"); + info!("User {user:?} ack in {channel} with {event:?}"); } } } @@ -123,21 +177,45 @@ pub async fn worker(db: Database, amqp: AMQP) { mut event, }) = Q.try_pop() { - match &mut event { - // this is a bit of a test, we're no longer delaying/batching AddMentions in an effort for - // pushd to have the mention in the db when it sends a message notification. - AckEvent::AddMention { .. } => { - handle_ack_event(&event, &db, &amqp, &user, &channel).await; - } - AckEvent::AckMessage { .. } => { - let key = (user, channel); - if let Some(task) = tasks.get_mut(&key) { - task.delay(); + let key: (Option, String, u8) = ( + user, + channel, + match &event { + AckEvent::AckMessage { .. } => 0, + AckEvent::ProcessMessage { .. } => 1, + }, + ); + if let Some(task) = tasks.get_mut(&key) { + match &mut event { + AckEvent::ProcessMessage { messages: new_data } => { + if let AckEvent::ProcessMessage { messages: existing } = + &mut task.data.event + { + // add the new message to the list of messages to be processed. + existing.append(new_data); + + // put a cap on the amount of messages that can be queued, for particularly active channels + if (existing.length() as u16) + < revolt_config::config() + .await + .features + .advanced + .process_message_delay_limit + { + task.delay(); + } + } else { + panic!("Somehow got an ack message in the add mention arm"); + } + } + AckEvent::AckMessage { .. } => { + // replace the last acked message with the new acked message task.data.event = event; - } else { - tasks.insert(key, DelayedTask::new(Task { event })); + task.delay(); } } + } else { + tasks.insert(key, DelayedTask::new(Task { event })); } } From 8672388c549f962dd859a8e2901953a96c08f878 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 29 Sep 2024 08:09:26 -0700 Subject: [PATCH 18/44] chore: update readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 48a770e71..ba24900f1 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ The services and libraries that power the Revolt service.
| `services/january` | [crates/services/january](crates/services/january) | Proxy server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) | | `services/autumn` | [crates/services/autumn](crates/services/autumn) | File server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) | | `bindings/node` | [crates/bindings/node](crates/bindings/node) | Node.js bindings for the Revolt software | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) | +| `daemons/pushd` | [crates/daemons/pushd](crates/daemons/pushd) | Push notification daemon server | ![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue) |
@@ -60,6 +61,7 @@ As a heads-up, the development environment uses the following ports: | MinIO | 14009 | | Maildev | 14025
14080 | | Revolt Web App | 14701 | +| RabbitMQ | 5672
15672 | | `crates/delta` | 14702 | | `crates/bonfire` | 14703 | | `crates/services/autumn` | 14704 | @@ -103,6 +105,8 @@ cargo run --bin revolt-bonfire cargo run --bin revolt-autumn # run th proxy server cargo run --bin revolt-january +# run the push daemon (not usually needed in regular development) +cargo run --bin revolt-pushd # hint: # mold -run From 41fdb0d2e15875b93c78cf32de875a6da44c0a45 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 5 Oct 2024 00:40:49 -0700 Subject: [PATCH 19/44] chore: optimizations for ack database hits --- crates/core/database/src/tasks/ack.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index f92c3563d..62baf6739 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -118,12 +118,14 @@ pub async fn handle_ack_event( .map(|(_, message, _, _)| message.id.clone()) .collect(); - db.add_mention_to_unread(channel, user, &message_ids) - .await?; + if !message_ids.is_empty() { + db.add_mention_to_unread(channel, user, &message_ids) + .await?; + } } for (push, _, recipients, silenced) in messages { - if *silenced || push.is_none() { + if *silenced || recipients.is_empty() || push.is_none() { continue; } From 0340f6584882ed41d326d620287248180eed69ac Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 5 Oct 2024 23:12:45 -0700 Subject: [PATCH 20/44] pushd flowchart --- crates/daemons/pushd/Pushd Flowchart.graffle | Bin 0 -> 48248 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 crates/daemons/pushd/Pushd Flowchart.graffle diff --git a/crates/daemons/pushd/Pushd Flowchart.graffle b/crates/daemons/pushd/Pushd Flowchart.graffle new file mode 100644 index 0000000000000000000000000000000000000000..b38961ca6399073b36aa4b0893c0443d533cfcf9 GIT binary patch literal 48248 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 literal 0 HcmV?d00001 From b9e6843535cf4c541660e70a27cf2cdf54e25fcf Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 12 Oct 2024 12:52:19 -0700 Subject: [PATCH 21/44] misc: update flowchart --- crates/daemons/pushd/Pushd Flowchart.graffle | Bin 48248 -> 45755 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/daemons/pushd/Pushd Flowchart.graffle b/crates/daemons/pushd/Pushd Flowchart.graffle index b38961ca6399073b36aa4b0893c0443d533cfcf9..961d5ff61319007beb4e3d9008c648c11425f422 100644 GIT binary patch literal 45755 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 literal 48248 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 From f12868377503742385af260a0474940124ba9685 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 20 Oct 2024 16:08:15 -0700 Subject: [PATCH 22/44] exit dependancy hell --- Cargo.lock | 2608 ++++++++++++++----------------- crates/bonfire/Cargo.toml | 2 +- crates/core/database/Cargo.toml | 6 +- crates/core/result/Cargo.toml | 2 +- crates/delta/Cargo.toml | 4 +- 5 files changed, 1148 insertions(+), 1474 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3422d978c..588642a8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -30,7 +30,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.7", + "generic-array 0.14.5", ] [[package]] @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom", "once_cell", @@ -76,7 +76,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -99,9 +98,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "amqp_serde" @@ -109,22 +108,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "787581044ca08ad61cdb3a4e21afba087fb8cdba3bb3e23ce69a7d091808014d" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "serde", "serde_bytes_ng", ] [[package]] name = "amqprs" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4173302f657e24f1b914edc992549077ec7f4971599e1aea42a01b17eb6e079" +checksum = "3f1b4afcbd862e16c272b7625b6b057930b052d63c720bc90f6afab0d9abe8a8" dependencies = [ "amqp_serde", "async-trait", - "bytes 1.7.1", + "bytes 1.5.0", "serde", - "serde_bytes", + "serde_bytes_ng", "tokio 1.40.0", ] @@ -143,6 +142,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -157,9 +165,9 @@ checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "arg_enum_proc_macro" @@ -169,14 +177,14 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" @@ -191,17 +199,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] name = "async-channel" -version = "1.9.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", + "concurrent-queue 1.2.2", + "event-listener 2.5.2", "futures-core", ] @@ -211,37 +219,38 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ - "concurrent-queue", + "concurrent-queue 2.5.0", "event-listener-strategy", "futures-core", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", ] [[package]] name = "async-executor" -version = "1.13.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" dependencies = [ "async-task", - "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.3.0", + "concurrent-queue 1.2.2", + "fastrand 1.7.0", + "futures-lite", + "once_cell", "slab", ] [[package]] name = "async-global-executor" -version = "2.4.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880" dependencies = [ - "async-channel 2.3.1", + "async-channel 1.6.1", "async-executor", - "async-io 2.3.4", - "async-lock 3.4.0", + "async-io", + "async-lock 2.8.0", "blocking", - "futures-lite 2.3.0", + "futures-lite", "once_cell", "tokio 0.2.25", "tokio 1.40.0", @@ -249,41 +258,21 @@ dependencies = [ [[package]] name = "async-io" -version = "1.13.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ - "async-lock 2.8.0", - "autocfg 1.3.0", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", + "concurrent-queue 1.2.2", + "futures-lite", + "libc", "log", + "once_cell", "parking", - "polling 2.8.0", - "rustix 0.37.27", + "polling", "slab", - "socket2 0.4.10", + "socket2 0.4.4", "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" -dependencies = [ - "async-lock 3.4.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.3", - "rustix 0.38.35", - "slab", - "tracing", - "windows-sys 0.59.0", + "winapi", ] [[package]] @@ -292,7 +281,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener 2.5.3", + "event-listener 2.5.2", ] [[package]] @@ -303,24 +292,24 @@ checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener 5.3.1", "event-listener-strategy", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", ] [[package]] name = "async-process" -version = "1.8.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", + "async-io", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.35", - "windows-sys 0.48.0", + "event-listener 2.5.2", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi", ] [[package]] @@ -331,25 +320,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", -] - -[[package]] -name = "async-signal" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" -dependencies = [ - "async-io 2.3.4", - "async-lock 3.4.0", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 0.38.35", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", + "syn 2.0.76", ] [[package]] @@ -359,22 +330,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", - "async-channel 1.9.0", + "async-channel 1.6.1", "async-global-executor", - "async-io 1.13.0", + "async-io", "async-lock 2.8.0", "async-process", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "once_cell", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "pin-utils", "slab", "wasm-bindgen-futures", @@ -391,47 +362,46 @@ dependencies = [ "futures-io", "futures-util", "pin-utils", - "socket2 0.4.10", + "socket2 0.4.4", "trust-dns-resolver", ] [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite 0.2.14", ] [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] name = "async-task" -version = "4.7.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -444,30 +414,24 @@ dependencies = [ "futures-io", "futures-util", "log", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tungstenite", ] [[package]] name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic" -version = "0.6.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" dependencies = [ - "bytemuck", + "autocfg 1.1.0", ] [[package]] name = "atomic-waker" -version = "1.1.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" @@ -493,7 +457,7 @@ dependencies = [ "chrono", "futures", "handlebars", - "iso8601-timestamp 0.1.11", + "iso8601-timestamp 0.1.10", "lazy_static", "lettre", "log", @@ -501,9 +465,9 @@ dependencies = [ "nanoid", "rand 0.8.5", "regex", - "reqwest 0.11.27", + "reqwest 0.11.10", "revolt_okapi", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.9.1", "rocket", "rust-argon2", "schemars", @@ -526,14 +490,14 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.1.0", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "av1-grain" @@ -576,7 +540,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.1", + "bytes 1.5.0", "fastrand 2.1.1", "hex", "http 0.2.12", @@ -615,22 +579,22 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.1", + "bytes 1.5.0", "fastrand 2.1.1", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "once_cell", "percent-encoding", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tracing", - "uuid", + "uuid 1.4.1", ] [[package]] name = "aws-sdk-s3" -version = "1.48.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a545b16c05af9302b0b4b38a7584f6f323749e407169aa3e9b210e7c0a808d" +checksum = "4abf69a87be33b6f125a93d5046b5f7395c26d1f449bf8d3927f5577463b6de0" dependencies = [ "ahash 0.8.11", "aws-credential-types", @@ -646,12 +610,12 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.7.1", + "bytes 1.5.0", "fastrand 2.1.1", "hex", "hmac", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "lru 0.12.4", "once_cell", "percent-encoding", @@ -663,9 +627,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.41.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0a3f676cba2c079c9563acc9233998c8951cdbe38629a0bef3c8c1b02f3658" +checksum = "11822090cf501c316c6f75711d77b96fba30658e3867a7762e5e2f5d32d31e81" dependencies = [ "aws-credential-types", "aws-runtime", @@ -676,7 +640,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.1", + "bytes 1.5.0", "http 0.2.12", "once_cell", "regex-lite", @@ -698,7 +662,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.1", + "bytes 1.5.0", "http 0.2.12", "once_cell", "regex-lite", @@ -707,9 +671,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.41.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c56bcd6a56cab7933980a54148b476a5a69a7694e3874d9aa2a566f150447d" +checksum = "a20a91795850826a6f456f4a48eff1dfa59a0e69bdbf5b8c50518fd372106574" dependencies = [ "aws-credential-types", "aws-runtime", @@ -739,7 +703,7 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "crypto-bigint 0.5.5", "form_urlencoded", "hex", @@ -747,7 +711,7 @@ dependencies = [ "http 0.2.12", "http 1.1.0", "once_cell", - "p256 0.11.1", + "p256", "percent-encoding", "ring 0.17.8", "sha2", @@ -764,7 +728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" dependencies = [ "futures-util", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", ] @@ -776,14 +740,14 @@ checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" dependencies = [ "aws-smithy-http", "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "crc32c", "crc32fast", "hex", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "md-5", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "sha1", "sha2", "tracing", @@ -796,7 +760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858" dependencies = [ "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "crc32fast", ] @@ -809,14 +773,14 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "bytes-utils", "futures-core", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "once_cell", "percent-encoding", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "pin-utils", "tracing", ] @@ -850,17 +814,17 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "fastrand 2.1.1", "h2 0.3.26", "http 0.2.12", - "http-body 0.4.6", - "http-body 1.0.1", + "http-body 0.4.5", + "http-body 1.0.0", "httparse", "hyper 0.14.30", "hyper-rustls 0.24.2", "once_cell", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "pin-utils", "rustls 0.21.12", "tokio 1.40.0", @@ -875,10 +839,10 @@ checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "bytes 1.7.1", + "bytes 1.5.0", "http 0.2.12", "http 1.1.0", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", "tracing", "zeroize", @@ -891,17 +855,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "273dcdfd762fae3e1650b8024624e7cd50e484e37abdab73a7a706188ad34543" dependencies = [ "base64-simd", - "bytes 1.7.1", + "bytes 1.5.0", "bytes-utils", "futures-core", "http 0.2.12", "http 1.1.0", - "http-body 0.4.6", - "http-body 1.0.1", + "http-body 0.4.5", + "http-body 1.0.0", "http-body-util", "itoa", "num-integer", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "pin-utils", "ryu", "serde", @@ -929,7 +893,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "tracing", ] @@ -941,12 +905,12 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "bytes 1.7.1", + "bytes 1.5.0", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "http-body-util", - "hyper 1.4.1", + "hyper 1.3.1", "hyper-util", "itoa", "matchit", @@ -954,7 +918,7 @@ dependencies = [ "mime", "multer", "percent-encoding", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "rustversion", "serde", "serde_json", @@ -975,13 +939,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", - "bytes 1.7.1", + "bytes 1.5.0", "futures-util", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "http-body-util", "mime", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "rustversion", "sync_wrapper 0.1.2", "tower-layer", @@ -997,14 +961,14 @@ checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" dependencies = [ "axum", "axum-core", - "bytes 1.7.1", + "bytes 1.5.0", "futures-util", "headers", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "http-body-util", "mime", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "serde", "tower", "tower-layer", @@ -1018,10 +982,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" dependencies = [ - "heck 0.4.1", + "heck 0.4.0", "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -1033,14 +997,14 @@ dependencies = [ "anyhow", "axum", "axum_typed_multipart_macros", - "bytes 1.7.1", + "bytes 1.5.0", "chrono", "futures-core", "futures-util", "tempfile", "thiserror", "tokio 1.40.0", - "uuid", + "uuid 1.4.1", ] [[package]] @@ -1053,21 +1017,21 @@ dependencies = [ "heck 0.5.0", "proc-macro-error", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", "ubyte", ] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide 0.5.3", "object", "rustc-demangle", ] @@ -1078,12 +1042,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base32" version = "0.4.0" @@ -1092,15 +1050,15 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] name = "base64" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64" -version = "0.21.7" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64" @@ -1176,69 +1134,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" [[package]] -name = "bitvec" -version = "1.0.1" +name = "blake2b_simd" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ - "funty", - "radium", - "tap", - "wyz", + "arrayref", + "arrayvec", + "constant_time_eq", ] [[package]] -name = "blake2b_simd" -version = "1.0.2" +name = "block-buffer" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", ] [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.5", +] + +[[package]] +name = "block-padding" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" dependencies = [ - "generic-array 0.14.7", + "byte-tools", ] [[package]] name = "blocking" -version = "1.6.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ - "async-channel 2.3.1", + "async-channel 1.6.1", "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", + "atomic-waker", + "fastrand 1.7.0", + "futures-lite", + "once_cell", ] [[package]] name = "bson" -version = "2.11.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a88e82b9106923b5c4d6edfca9e7db958d4e98a478ec115022e81b9b38e2c8" +checksum = "f60a2c7c80a7850b56df4b8e98e8e4932c34877b8add4f13e8350499cc1e4572" dependencies = [ - "ahash 0.8.11", - "base64 0.13.1", - "bitvec", + "ahash 0.7.6", + "base64 0.13.0", + "chrono", "hex", - "indexmap 2.5.0", - "js-sys", - "once_cell", + "indexmap 1.9.3", + "lazy_static", "rand 0.8.5", "serde", "serde_bytes", "serde_json", - "time", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -1249,9 +1215,15 @@ checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" @@ -1261,9 +1233,9 @@ checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "byteorder-lite" @@ -1279,9 +1251,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.7.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytes-utils" @@ -1289,10 +1261,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "either", ] +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cached" version = "0.44.0" @@ -1320,14 +1298,14 @@ dependencies = [ "darling 0.14.4", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] name = "cached_proc_macro_types" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" [[package]] name = "castaway" @@ -1354,7 +1332,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid", + "uuid 1.4.1", ] [[package]] @@ -1408,12 +1386,13 @@ dependencies = [ [[package]] name = "coarsetime" -version = "0.1.34" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" dependencies = [ "libc", - "wasix", + "once_cell", + "wasi", "wasm-bindgen", ] @@ -1425,18 +1404,27 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-core", "memchr", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", "tokio-util", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1448,9 +1436,9 @@ dependencies = [ [[package]] name = "config" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" dependencies = [ "async-trait", "json5", @@ -1461,7 +1449,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.5.11", + "toml 0.5.9", "yaml-rust", ] @@ -1473,21 +1461,15 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" [[package]] name = "const-oid" -version = "0.9.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "convert_case" -version = "0.4.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cookie" @@ -1502,18 +1484,15 @@ dependencies = [ [[package]] name = "cookie-factory" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -1521,15 +1500,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -1546,7 +1525,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ - "rustc_version 0.4.1", + "rustc_version 0.4.0", ] [[package]] @@ -1588,10 +1567,11 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ + "cfg-if", "crossbeam-utils", ] @@ -1613,7 +1593,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.5", "rand_core 0.6.4", "subtle", "zeroize", @@ -1625,10 +1605,8 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array 0.14.7", "rand_core 0.6.4", "subtle", - "zeroize", ] [[package]] @@ -1637,16 +1615,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.5", "rand_core 0.6.4", "typenum", ] [[package]] name = "ct-codecs" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "026ac6ceace6298d2c557ef5ed798894962296469ec7842288ea64674201a2d1" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" [[package]] name = "ctr" @@ -1659,24 +1637,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.7", - "windows-sys 0.52.0", + "socket2 0.4.4", + "winapi", ] [[package]] name = "curl-sys" -version = "0.4.74+curl-8.9.0" +version = "0.4.65+curl-8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" dependencies = [ "cc", "libc", @@ -1685,7 +1663,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys 0.52.0", + "winapi", ] [[package]] @@ -1729,7 +1707,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "strsim 0.10.0", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -1743,7 +1721,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "strsim 0.10.0", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -1757,7 +1735,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -1768,7 +1746,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core 0.13.4", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -1779,7 +1757,7 @@ checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -1790,27 +1768,26 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "dashmap" -version = "5.5.3" +version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" dependencies = [ "cfg-if", - "hashbrown 0.14.5", + "hashbrown 0.12.1", "lock_api", - "once_cell", "parking_lot_core", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "deadqueue" @@ -1829,14 +1806,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid", + "uuid 1.4.1", ] [[package]] name = "decancer" -version = "1.6.5" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080b09f6adad25c23d8c47c54e52e59b0dc09d079c4b23e0f147dac440359d0d" +checksum = "808127a7de612079ec37bfc1abc48ed77a6015a971a8bd7d4178d79147cbc839" [[package]] name = "der" @@ -1854,19 +1831,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.6.0", - "zeroize", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", + "const-oid 0.9.5", + "pem-rfc7468", "zeroize", ] @@ -1878,20 +1844,10 @@ checksum = "8aed3b3c608dc56cf36c45fe979d04eda51242e6703d8d0bb03426ef7c41db6a" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", "synstructure", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - [[package]] name = "derivative" version = "2.2.0" @@ -1900,20 +1856,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" -dependencies = [ - "convert_case", - "proc-macro2", - "quote 1.0.37", - "rustc_version 0.4.1", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] @@ -1946,7 +1889,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", ] [[package]] @@ -1955,8 +1907,8 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid 0.9.6", + "block-buffer 0.10.2", + "const-oid 0.9.5", "crypto-common", "subtle", ] @@ -1975,9 +1927,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "ecdsa" @@ -1986,32 +1938,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der 0.7.9", - "digest", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] name = "ece" -version = "2.3.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ea1d2f2cc974957a4e2575d8e5bb494549bab66338d6320c2789abcfff5746" +checksum = "8dd5463ffecc0677adcd786c4481f73b215714d4757edf2eb37a573c03d00459" dependencies = [ - "base64 0.21.7", + "base64 0.13.0", "byteorder", "hex", "hkdf", @@ -2025,9 +1963,9 @@ dependencies = [ [[package]] name = "ed25519-compact" -version = "2.1.1" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c" dependencies = [ "ct-codecs", "getrandom", @@ -2035,9 +1973,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "elliptic-curve" @@ -2045,57 +1983,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct 0.1.1", + "base16ct", "crypto-bigint 0.4.9", "der 0.6.1", - "digest", - "ff 0.12.1", - "generic-array 0.14.7", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", - "digest", - "ff 0.13.0", - "generic-array 0.14.7", - "group 0.13.0", + "digest 0.10.7", + "ff", + "generic-array 0.14.5", + "group", "hkdf", - "pem-rfc7468 0.7.0", - "pkcs8 0.10.2", + "pem-rfc7468", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.3", + "sec1", "subtle", "zeroize", ] [[package]] name = "email-encoding" -version = "0.2.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87260449b06739ee78d6281c68d2a0ff3e3af64a78df63d3a1aeb3c06997c8a" +checksum = "6690291166824e467790ac08ba42f241791567e8337bbf00c5a6e87889629f98" dependencies = [ - "base64 0.22.1", - "memchr", + "base64 0.13.0", ] -[[package]] -name = "email_address" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" - [[package]] name = "encoding_rs" version = "0.8.34" @@ -2111,30 +2023,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck 0.4.1", + "heck 0.4.0", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] name = "enum-iterator" -version = "1.5.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" +checksum = "91a4ec26efacf4aeff80887a175a419493cb6f8b5480d26387eb0bd038976187" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "1.4.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] @@ -2158,9 +2070,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.31" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" dependencies = [ "serde", ] @@ -2177,20 +2089,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite 0.2.14", -] +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" [[package]] name = "event-listener" @@ -2198,9 +2099,9 @@ version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ - "concurrent-queue", + "concurrent-queue 2.5.0", "parking", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", ] [[package]] @@ -2210,7 +2111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener 5.3.1", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", ] [[package]] @@ -2230,12 +2131,18 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.9.0" +name = "fake-simd" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", ] [[package]] @@ -2251,7 +2158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc9ed4ed7618b2f7581edb6ce28794a10fcc335e196fc2bcf56afee99ad63d10" dependencies = [ "async-trait", - "reqwest 0.11.27", + "reqwest 0.11.10", "serde", "serde_json", "yup-oauth2", @@ -2276,16 +2183,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "ffprobe" version = "0.4.0" @@ -2298,14 +2195,14 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.19" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" dependencies = [ - "atomic 0.6.0", + "atomic", "pear", "serde", - "toml 0.8.19", + "toml 0.5.9", "uncased", "version_check", ] @@ -2382,23 +2279,24 @@ dependencies = [ [[package]] name = "fred" -version = "8.0.6" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8e3a1339ed45ad8fde94530c4bdcbd5f371a3c6bd3bf57682923792830aa37" +checksum = "d3b2a2ac060e3266004c552235c241b481e438e2b1ea75715ea1176914ef2868" dependencies = [ "arc-swap", "async-trait", - "bytes 1.7.1", + "bytes 1.5.0", "bytes-utils", "crossbeam-queue", "float-cmp", "futures", + "lazy_static", "log", "parking_lot", "rand 0.8.5", "redis-protocol", "semver 1.0.23", - "socket2 0.5.7", + "socket2 0.5.5", "tokio 1.40.0", "tokio-stream", "tokio-util", @@ -2412,17 +2310,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" -version = "0.3.30" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -2451,9 +2343,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -2468,32 +2360,19 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ - "fastrand 1.9.0", + "fastrand 1.7.0", "futures-core", "futures-io", "memchr", "parking", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "waker-fn", ] -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.1", - "futures-core", - "futures-io", - "parking", - "pin-project-lite 0.2.14", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -2513,7 +2392,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -2530,9 +2409,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" @@ -2547,49 +2426,48 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "pin-utils", "slab", ] [[package]] name = "generator" -version = "0.7.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" dependencies = [ "cc", "libc", "log", "rustversion", - "windows", + "winapi", ] [[package]] name = "generic-array" -version = "0.14.7" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" dependencies = [ "typenum", - "version_check", - "zeroize", ] [[package]] name = "generic-array" -version = "1.1.0" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", + "version_check", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -2607,7 +2485,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -2616,7 +2494,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug", + "opaque-debug 0.3.0", "polyval", ] @@ -2632,15 +2510,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "git2" -version = "0.16.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" +checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ "bitflags 1.3.2", "libc", @@ -2651,15 +2529,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", @@ -2673,18 +2551,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff 0.13.0", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2695,13 +2562,13 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "fnv", "futures-core", "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.0.1", "slab", "tokio 1.40.0", "tokio-util", @@ -2710,17 +2577,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ "atomic-waker", - "bytes 1.7.1", + "bytes 1.5.0", "fnv", "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap 2.0.1", "slab", "tokio 1.40.0", "tokio-util", @@ -2739,9 +2606,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.5.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +checksum = "d113a9853e5accd30f43003560b5563ffbb007e3f325e8b103fa0d0029c6e6df" dependencies = [ "log", "pest", @@ -2753,11 +2620,20 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" dependencies = [ - "ahash 0.7.8", + "ahash 0.7.6", ] [[package]] @@ -2768,9 +2644,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2782,8 +2658,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ - "base64 0.21.7", - "bytes 1.7.1", + "base64 0.21.3", + "bytes 1.5.0", "headers-core", "http 1.1.0", "httpdate", @@ -2802,9 +2678,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "heck" @@ -2841,9 +2717,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ "hmac", ] @@ -2854,7 +2730,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2869,7 +2745,7 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2878,7 +2754,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2898,7 +2774,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "fnv", "itoa", ] @@ -2909,29 +2785,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "http 0.2.12", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "http 1.1.0", ] @@ -2941,11 +2817,11 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-util", "http 1.1.0", - "http-body 1.0.1", - "pin-project-lite 0.2.14", + "http-body 1.0.0", + "pin-project-lite 0.2.13", ] [[package]] @@ -2956,9 +2832,9 @@ checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -2975,18 +2851,18 @@ version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-channel", "futures-core", "futures-util", "h2 0.3.26", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.14", - "socket2 0.5.7", + "pin-project-lite 0.2.13", + "socket2 0.5.5", "tokio 1.40.0", "tower-service", "tracing", @@ -2995,20 +2871,20 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.5", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "smallvec", "tokio 1.40.0", "want", @@ -3038,31 +2914,14 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.3.1", "hyper-util", "rustls 0.22.4", "rustls-pki-types", "tokio 1.40.0", "tokio-rustls 0.25.0", "tower-service", - "webpki-roots 0.26.5", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.4.1", - "hyper-util", - "rustls 0.23.12", - "rustls-pki-types", - "tokio 1.40.0", - "tokio-rustls 0.26.0", - "tower-service", + "webpki-roots 0.26.3", ] [[package]] @@ -3071,7 +2930,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "hyper 0.14.30", "native-tls", "tokio 1.40.0", @@ -3084,9 +2943,9 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "http-body-util", - "hyper 1.4.1", + "hyper 1.3.1", "hyper-util", "native-tls", "tokio 1.40.0", @@ -3096,18 +2955,18 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.1", - "hyper 1.4.1", - "pin-project-lite 0.2.14", - "socket2 0.5.7", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite 0.2.13", + "socket2 0.5.5", "tokio 1.40.0", "tower", "tower-service", @@ -3154,26 +3013,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -3247,19 +3086,19 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg 1.3.0", - "hashbrown 0.12.3", + "autocfg 1.1.0", + "hashbrown 0.12.1", "serde", ] [[package]] name = "indexmap" -version = "2.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.14.0", "serde", ] @@ -3284,14 +3123,14 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.5", ] [[package]] name = "instant" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -3304,37 +3143,26 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "syn 2.0.76", ] [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.5.7", + "socket2 0.4.4", "widestring", - "windows-sys 0.48.0", - "winreg", + "winapi", + "winreg 0.7.0", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "is-terminal" @@ -3353,19 +3181,19 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ - "async-channel 1.9.0", + "async-channel 1.6.1", "castaway", "crossbeam-utils", "curl", "curl-sys", "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", + "event-listener 2.5.2", + "futures-lite", "http 0.2.12", "log", "mime", "once_cell", - "polling 2.8.0", + "polling", "serde", "serde_json", "slab", @@ -3378,22 +3206,22 @@ dependencies = [ [[package]] name = "iso8601-timestamp" -version = "0.1.11" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b088f3296223f11d6bd70998132653b30b6f39e6ab4be055c0bda486fa8d11c7" +checksum = "56480a7792bfa284a4c7d1781f711de58e357dbb669c1b1a50c37c1777c9be92" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.5", "serde", "time", ] [[package]] name = "iso8601-timestamp" -version = "0.2.17" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d4e5d712dd664b11e778d1cfc06c79ba2700d6bc1771e44fb7b6a4656b487d" +checksum = "3bd7663ae5b511b3d13c7d374dbe73b03da3da8e66b83950390173bc7c7325cc" dependencies = [ - "generic-array 1.1.0", + "generic-array 0.14.5", "schemars", "serde", "time", @@ -3431,9 +3259,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -3451,9 +3279,9 @@ dependencies = [ [[package]] name = "jwt-simple" -version = "0.11.9" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357892bb32159d763abdea50733fadcb9a8e1c319a9aa77592db8555d05af83e" +checksum = "529a00f2d42d7dc349c994e65917c81bf53225831a65361f6c0454124c550f63" dependencies = [ "anyhow", "binstring", @@ -3464,13 +3292,13 @@ dependencies = [ "hmac-sha256", "hmac-sha512", "k256", - "p256 0.13.2", + "p256", "p384", "rand 0.8.5", "rsa", "serde", "serde_json", - "spki 0.6.0", + "spki", "thiserror", "zeroize", ] @@ -3618,16 +3446,14 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "once_cell", + "ecdsa", + "elliptic-curve", "sha2", - "signature 2.2.0", ] [[package]] @@ -3665,32 +3491,30 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "lettre" -version = "0.10.4" +version = "0.10.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" +checksum = "2f6c70001f7ee6c93b6687a06607c7a38f9a7ae460139a496c23da21e95bc289" dependencies = [ - "base64 0.21.7", + "base64 0.13.0", "email-encoding", - "email_address", - "fastrand 1.9.0", + "fastrand 1.7.0", "futures-util", "hostname", "httpdate", - "idna 0.3.0", + "idna 0.2.3", "mime", "native-tls", "nom", "once_cell", "quoted_printable", - "socket2 0.4.10", - "tokio 1.40.0", + "regex", ] [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libfuzzer-sys" @@ -3705,9 +3529,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.14.2+1.5.1" +version = "0.14.1+1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" +checksum = "4a07fb2692bc3593bda59de45a502bb3071659f2c515e28c71e728306b038e17" dependencies = [ "cc", "libc", @@ -3727,15 +3551,15 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libnghttp2-sys" -version = "0.1.10+1.61.0" +version = "0.1.8+1.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959c25552127d2e1fa72f0e52548ec04fc386e827ba71a7bd01db46a447dc135" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" dependencies = [ "cc", "libc", @@ -3753,9 +3577,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -3765,9 +3589,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "linkify" @@ -3787,12 +3611,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -3801,11 +3619,11 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.1.0", "scopeguard", ] @@ -3844,20 +3662,20 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +checksum = "8015d95cb7b2ddd3c0d32ca38283ceb1eea09b4713ee380bceb942d85a244228" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.11.2", ] [[package]] name = "lru" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" +checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.14.0", ] [[package]] @@ -3866,7 +3684,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.14.0", ] [[package]] @@ -3884,6 +3702,12 @@ version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -3901,9 +3725,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matchit" @@ -3923,12 +3747,11 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.6" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" dependencies = [ - "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -3939,12 +3762,23 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "metrics" -version = "0.22.3" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +checksum = "2e52eb6380b6d2a10eb3434aec0885374490f5b82c8aaf5cd487a183c98be834" dependencies = [ - "ahash 0.8.11", - "portable-atomic", + "ahash 0.7.6", + "metrics-macros", +] + +[[package]] +name = "metrics-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e30813093f757be5cf21e50389a24dc7dbb22c49f23b7e8f51d69b508a5ffa" +dependencies = [ + "proc-macro2", + "quote 1.0.37", + "syn 1.0.107", ] [[package]] @@ -3959,6 +3793,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -3992,9 +3835,9 @@ dependencies = [ [[package]] name = "mobc" -version = "0.8.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d3681f0b299413df040f53c6950de82e48a8e1a9f79d442ed1ad3694d660b9" +checksum = "fc79c4a77e312fee9c7bd4b957c12ad1196db73c4a81e5c0b13f02083c4f7f2f" dependencies = [ "async-std", "async-trait", @@ -4012,12 +3855,12 @@ dependencies = [ [[package]] name = "mobc-redis" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e90cc63bd181da5c1f826427d26a63ab0ad1927462b2411aaa86e5f6be3d6b1" +checksum = "7bd8e2fd6bf7e35263b86662e663a9496a0352ceddd413b6c33313c36d5068fd" dependencies = [ "mobc", - "redis", + "redis 0.22.3", ] [[package]] @@ -4036,48 +3879,47 @@ dependencies = [ "once_cell", "parking_lot", "quanta", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "smallvec", "tagptr", "thiserror", "triomphe", - "uuid", + "uuid 1.4.1", ] [[package]] name = "mongodb" -version = "2.6.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16928502631c0db72214720aa479c722397fe5aed6bf1c740a3830b3fe4bfcfe" +checksum = "28f3943e379e9dcaaab9dc319c308a8caaf9e7ff083c6838dff740afbba59df7" dependencies = [ "async-std", "async-std-resolver", "async-trait", - "base64 0.13.1", + "base64 0.13.0", "bitflags 1.3.2", "bson", "chrono", "derivative", - "derive_more", "futures-core", "futures-executor", - "futures-io", "futures-util", "hex", "hmac", "lazy_static", "md-5", + "os_info", "pbkdf2", "percent-encoding", "rand 0.8.5", "rustc_version_runtime", - "rustls 0.20.9", - "rustls-pemfile 1.0.4", + "rustls 0.20.6", + "rustls-pemfile 0.3.0", "serde", "serde_with", - "sha-1", + "sha-1 0.10.0", "sha2", - "socket2 0.4.10", + "socket2 0.4.4", "stringprep", "strsim 0.10.0", "take_mut", @@ -4088,8 +3930,8 @@ dependencies = [ "trust-dns-proto", "trust-dns-resolver", "typed-builder", - "uuid", - "webpki-roots 0.22.6", + "uuid 0.8.2", + "webpki-roots 0.22.3", ] [[package]] @@ -4098,7 +3940,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "encoding_rs", "futures-util", "http 1.1.0", @@ -4128,10 +3970,11 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ + "lazy_static", "libc", "log", "openssl", @@ -4165,7 +4008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6813fde79b646e47e7ad75f480aa80ef76a5d9599e2717407961531169ee38b" dependencies = [ "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", "syn-mid", ] @@ -4188,9 +4031,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nom" -version = "7.1.3" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", @@ -4204,20 +4047,10 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" dependencies = [ - "overload", "winapi", ] @@ -4270,12 +4103,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.4.2" @@ -4284,7 +4111,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -4302,7 +4129,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.1.0", "num-integer", "num-traits", ] @@ -4324,27 +4151,27 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.1.0", "libm", ] [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.1.19", "libc", ] [[package]] name = "num_enum" -version = "0.5.11" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" dependencies = [ - "num_enum_derive 0.5.11", + "num_enum_derive 0.5.7", ] [[package]] @@ -4358,14 +4185,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -4377,7 +4204,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -4391,9 +4218,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "memchr", ] @@ -4406,17 +4233,23 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.1" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -4427,13 +4260,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] @@ -4444,10 +4277,11 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" dependencies = [ + "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -4461,18 +4295,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ "dlv-list", - "hashbrown 0.12.3", + "hashbrown 0.12.1", ] [[package]] name = "os_info" -version = "3.8.2" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" dependencies = [ "log", "serde", - "windows-sys 0.52.0", + "winapi", ] [[package]] @@ -4481,58 +4315,39 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", + "ecdsa", + "elliptic-curve", "sha2", ] [[package]] name = "p384" -version = "0.13.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", + "ecdsa", + "elliptic-curve", "sha2", ] [[package]] name = "parking" -version = "2.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -4540,15 +4355,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-sys 0.36.1", ] [[package]] @@ -4565,11 +4380,11 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -4592,7 +4407,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -4601,7 +4416,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "once_cell", "regex", ] @@ -4612,7 +4427,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", ] [[package]] @@ -4634,15 +4449,6 @@ dependencies = [ "base64ct", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -4651,20 +4457,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "memchr", - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" dependencies = [ "pest", "pest_generator", @@ -4672,46 +4476,46 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" dependencies = [ - "once_cell", + "maplit", "pest", - "sha2", + "sha-1 0.8.2", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] @@ -4722,9 +4526,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -4732,17 +4536,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand 2.1.1", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.4.1" @@ -4750,8 +4543,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" dependencies = [ "der 0.6.1", - "pkcs8 0.9.0", - "spki 0.6.0", + "pkcs8", + "spki", "zeroize", ] @@ -4762,24 +4555,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der 0.6.1", - "spki 0.6.0", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.9", - "spki 0.7.3", + "spki", ] [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "png" @@ -4796,33 +4579,15 @@ dependencies = [ [[package]] name = "polling" -version = "2.8.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "autocfg 1.3.0", - "bitflags 1.3.2", "cfg-if", - "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.14", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite 0.2.14", - "rustix 0.38.35", - "tracing", - "windows-sys 0.59.0", + "wepoll-ffi", + "winapi", ] [[package]] @@ -4833,30 +4598,15 @@ checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_env_logger" @@ -4868,23 +4618,14 @@ dependencies = [ "log", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve 0.13.8", -] - [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "thiserror", + "toml 0.5.9", ] [[package]] @@ -4896,7 +4637,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", "version_check", ] @@ -4928,7 +4669,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", "version_check", "yansi", ] @@ -4949,14 +4690,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "prometheus" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ "cfg-if", "fnv", @@ -5025,15 +4766,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49" - -[[package]] -name = "radium" -version = "0.7.0" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" [[package]] name = "rand" @@ -5263,17 +4998,17 @@ dependencies = [ [[package]] name = "redis" -version = "0.23.1" -source = "git+https://github.com/revoltchat/redis-rs?rev=f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9#f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" +version = "0.22.3" +source = "git+https://github.com/revoltchat/redis-rs?rev=1a41faf356fd21aebba71cea7eb7eb2653e5f0ef#1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" dependencies = [ "async-std", "async-trait", - "bytes 1.7.1", + "bytes 1.5.0", "combine", "futures-util", "itoa", "percent-encoding", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "ryu", "sha1_smol", "tokio 1.40.0", @@ -5281,6 +5016,26 @@ dependencies = [ "url", ] +[[package]] +name = "redis" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +dependencies = [ + "async-std", + "async-trait", + "bytes 1.5.0", + "combine", + "futures-util", + "itoa", + "percent-encoding", + "pin-project-lite 0.2.13", + "ryu", + "tokio 1.40.0", + "tokio-util", + "url", +] + [[package]] name = "redis-kiss" version = "0.1.4" @@ -5291,7 +5046,7 @@ dependencies = [ "lazy_static", "mobc", "mobc-redis", - "redis", + "redis 0.23.3", "rmp-serde", "serde", "serde_json", @@ -5303,7 +5058,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c31deddf734dc0a39d3112e73490e88b61a05e83e074d211f348404cee4d2c6" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "bytes-utils", "cookie-factory", "crc16", @@ -5313,43 +5068,43 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", ] [[package]] name = "ref-cast" -version = "1.0.23" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +checksum = "685d58625b6c2b83e4cc88a27c4bf65adb7b6b16dbdc413e515c9405b47432ab" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.23" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +checksum = "a043824e29c94169374ac5183ac0ed43f5724dc4556b19568007486bd840fa1f" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -5358,18 +5113,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax 0.6.29", + "regex-syntax 0.6.26", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -5380,73 +5135,68 @@ checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ - "base64 0.21.7", - "bytes 1.7.1", + "base64 0.13.0", + "bytes 1.5.0", "encoding_rs", "futures-core", "futures-util", "h2 0.3.26", "http 0.2.12", - "http-body 0.4.6", + "http-body 0.4.5", "hyper 0.14.30", "hyper-tls 0.5.0", "ipnet", "js-sys", + "lazy_static", "log", "mime", "native-tls", - "once_cell", "percent-encoding", - "pin-project-lite 0.2.14", - "rustls-pemfile 1.0.4", + "pin-project-lite 0.2.13", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", "tokio 1.40.0", "tokio-native-tls", - "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.10.1", ] [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ "base64 0.22.1", - "bytes 1.7.1", + "bytes 1.5.0", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.5", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.27.2", + "hyper 1.3.1", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -5456,13 +5206,13 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.14", - "rustls-pemfile 2.1.3", + "pin-project-lite 0.2.13", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", - "system-configuration 0.6.1", + "sync_wrapper 0.1.2", + "system-configuration", "tokio 1.40.0", "tokio-native-tls", "tower-service", @@ -5470,7 +5220,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", + "winreg 0.52.0", ] [[package]] @@ -5532,7 +5282,7 @@ dependencies = [ "fred", "futures", "log", - "lru 0.7.8", + "lru 0.7.6", "lru_time_cache", "once_cell", "querystring", @@ -5578,7 +5328,7 @@ dependencies = [ "async-trait", "authifier", "axum", - "base64 0.21.7", + "base64 0.21.3", "bson", "deadqueue", "decancer", @@ -5586,10 +5336,10 @@ dependencies = [ "futures", "indexmap 1.9.3", "isahc", - "iso8601-timestamp 0.2.17", + "iso8601-timestamp 0.2.11", "linkify 0.8.1", "log", - "lru 0.11.1", + "lru 0.11.0", "mongodb", "nanoid", "once_cell", @@ -5604,7 +5354,7 @@ dependencies = [ "revolt_a2", "revolt_okapi", "revolt_optional_struct", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.10.0", "rocket", "schemars", "serde", @@ -5612,7 +5362,7 @@ dependencies = [ "ulid 1.1.3", "unicode-segmentation", "url-escape", - "validator 0.16.1", + "validator 0.16.0", "web-push", ] @@ -5621,7 +5371,7 @@ name = "revolt-delta" version = "0.7.16" dependencies = [ "amqprs", - "async-channel 1.9.0", + "async-channel 1.6.1", "async-std", "authifier", "bitfield", @@ -5631,25 +5381,25 @@ dependencies = [ "env_logger", "futures", "impl_ops", - "iso8601-timestamp 0.2.17", + "iso8601-timestamp 0.2.11", "lettre", "linkify 0.6.0", "log", - "lru 0.7.8", + "lru 0.7.6", "nanoid", - "num_enum 0.5.11", + "num_enum 0.5.7", "once_cell", "rand 0.8.5", "redis-kiss", "regex", - "reqwest 0.11.27", + "reqwest 0.11.10", "revolt-config", "revolt-database", "revolt-models", "revolt-permissions", "revolt-presence", "revolt-result", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.10.0", "rocket", "rocket_authifier", "rocket_cors", @@ -5660,7 +5410,7 @@ dependencies = [ "serde_json", "ulid 0.4.1", "url", - "validator 0.16.1", + "validator 0.16.0", "vergen", ] @@ -5686,7 +5436,7 @@ dependencies = [ "lazy_static", "mime", "moka", - "reqwest 0.12.7", + "reqwest 0.12.4", "revolt-config", "revolt-models", "revolt-result", @@ -5704,7 +5454,7 @@ name = "revolt-models" version = "0.7.16" dependencies = [ "indexmap 1.9.3", - "iso8601-timestamp 0.2.17", + "iso8601-timestamp 0.2.11", "once_cell", "regex", "revolt-config", @@ -5714,7 +5464,7 @@ dependencies = [ "schemars", "serde", "utoipa", - "validator 0.16.1", + "validator 0.16.0", ] [[package]] @@ -5766,7 +5516,7 @@ dependencies = [ "base64 0.22.1", "fcm_v1", "isahc", - "iso8601-timestamp 0.2.17", + "iso8601-timestamp 0.2.11", "log", "revolt-config", "revolt-database", @@ -5786,7 +5536,7 @@ version = "0.7.16" dependencies = [ "axum", "revolt_okapi", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.10.0", "rocket", "schemars", "serde", @@ -5800,18 +5550,18 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edbe1f79cb41271d3cd8f932d75dddeba963c19dc93d1ee6cbe0391b495ab2f5" dependencies = [ - "base64 0.21.7", + "base64 0.21.3", "erased-serde", "http 1.1.0", "http-body-util", - "hyper 1.4.1", + "hyper 1.3.1", "hyper-rustls 0.26.0", "hyper-util", "parking_lot", "pem 3.0.4", "ring 0.17.8", "rustls 0.22.4", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.1.2", "serde", "serde_json", "thiserror", @@ -5862,6 +5612,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "revolt_rocket_okapi" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb113b281380c12c185c8d98c4887627ae6f7add16a510073382518ce34e42db" +dependencies = [ + "either", + "log", + "revolt_okapi", + "revolt_rocket_okapi_codegen", + "rocket", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "revolt_rocket_okapi_codegen" version = "0.9.1" @@ -5872,7 +5638,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "rocket_http", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -5886,16 +5652,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "rgb" version = "0.8.50" @@ -5937,9 +5693,9 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.14" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" dependencies = [ "byteorder", "num-traits", @@ -5948,9 +5704,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e" dependencies = [ "byteorder", "rmp", @@ -5965,19 +5721,19 @@ checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" dependencies = [ "async-stream", "async-trait", - "atomic 0.5.3", + "atomic", "binascii", - "bytes 1.7.1", + "bytes 1.5.0", "either", "figment", "futures", - "indexmap 2.5.0", + "indexmap 2.0.1", "log", "memchr", "multer", "num_cpus", "parking_lot", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "rand 0.8.5", "ref-cast", "rocket_codegen", @@ -6002,9 +5758,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f89a12311f60e9288833fc3ce6029bce5d5c61870ceef74d4a50668a8b520ad" dependencies = [ "authifier", - "iso8601-timestamp 0.1.11", + "iso8601-timestamp 0.1.10", "revolt_okapi", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.9.1", "rocket", "rocket_empty", "schemars", @@ -6019,12 +5775,12 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap 2.5.0", + "indexmap 2.0.1", "proc-macro2", "quote 1.0.37", "rocket_http", - "syn 2.0.77", - "unicode-xid 0.2.5", + "syn 2.0.76", + "unicode-xid 0.2.3", "version_check", ] @@ -6051,7 +5807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c0922e47f981204fee38578a8efcf47a5c1a4ac0eb7f59e6bdfa3e61c8e3d69" dependencies = [ "revolt_okapi", - "revolt_rocket_okapi", + "revolt_rocket_okapi 0.9.1", "rocket", ] @@ -6066,12 +5822,12 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.30", - "indexmap 2.5.0", + "indexmap 2.0.1", "log", "memchr", "pear", "percent-encoding", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "ref-cast", "serde", "smallvec", @@ -6084,9 +5840,9 @@ dependencies = [ [[package]] name = "rocket_prometheus" -version = "0.10.1" +version = "0.10.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f429d654a6854abef0a5361235e847b6552d5d989c2fc8a312ba4aebd8837f" +checksum = "e3e70efcf6b0234723d0d295b95d64283ed02bb98a8a3f0c8c7ebb5b8da69165" dependencies = [ "prometheus", "rocket", @@ -6098,7 +5854,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "bitflags 1.3.2", "serde", ] @@ -6110,15 +5866,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" dependencies = [ "byteorder", - "digest", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-iter", "num-traits", "pkcs1", - "pkcs8 0.9.0", + "pkcs8", "rand_core 0.6.4", - "signature 1.6.4", + "signature", "smallvec", "subtle", "zeroize", @@ -6126,11 +5882,11 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" dependencies = [ - "base64 0.21.7", + "base64 0.13.0", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -6148,9 +5904,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc_version" @@ -6163,9 +5919,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver 1.0.23", ] @@ -6182,36 +5938,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.20.9" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring 0.16.20", @@ -6240,20 +5982,7 @@ dependencies = [ "log", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.7", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki 0.102.7", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -6270,20 +5999,29 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.7", + "base64 0.21.3", ] [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -6291,9 +6029,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -6307,9 +6045,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring 0.17.8", "rustls-pki-types", @@ -6318,30 +6056,31 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "windows-sys 0.52.0", + "lazy_static", + "windows-sys 0.36.1", ] [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -6352,36 +6091,36 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" dependencies = [ "proc-macro2", "quote 1.0.37", "serde_derive_internals", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] name = "scoped-tls" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -6396,24 +6135,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct 0.1.1", + "base16ct", "der 0.6.1", - "generic-array 0.14.7", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct 0.2.0", - "der 0.7.9", - "generic-array 0.14.7", - "pkcs8 0.10.2", + "generic-array 0.14.5", + "pkcs8", "subtle", "zeroize", ] @@ -6431,11 +6156,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ - "bitflags 2.6.0", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -6444,9 +6169,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -6481,13 +6206,13 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "sentry" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce4b57f1b521f674df7a1d200be8ff5d74e3712020ee25b553146657b5377d5" +checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654" dependencies = [ "httpdate", "native-tls", - "reqwest 0.11.27", + "reqwest 0.11.10", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -6500,9 +6225,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cc8d4e04a73de8f718dc703943666d03f25d3e9e4d0fb271ca0b8c76dfa00e" +checksum = "11f2ee8f147bb5f22ac59b5c35754a759b9a6f6722402e2a14750b2a63fc59bd" dependencies = [ "backtrace", "once_cell", @@ -6512,23 +6237,23 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6436c1bad22cdeb02179ea8ef116ffc217797c028927def303bc593d9320c0d1" +checksum = "dcd133362c745151eeba0ac61e3ba8350f034e9fe7509877d08059fe1d7720c6" dependencies = [ "hostname", "libc", "os_info", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "sentry-core", "uname", ] [[package]] name = "sentry-core" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901f761681f97db3db836ef9e094acdd8756c40215326c194201941947164ef1" +checksum = "7163491708804a74446642ff2c80b3acd668d4b9e9f497f85621f3d250fd012b" dependencies = [ "once_cell", "rand 0.8.5", @@ -6539,9 +6264,9 @@ dependencies = [ [[package]] name = "sentry-debug-images" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdb263e73d22f39946f6022ed455b7561b22ff5553aca9be3c6a047fa39c328" +checksum = "6a5003d7ff08aa3b2b76994080b183e8cfa06c083e280737c9cee02ca1c70f5e" dependencies = [ "findshlibs", "once_cell", @@ -6550,9 +6275,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fbf1c163f8b6a9d05912e1b272afa27c652e8b47ea60cb9a57ad5e481eea99" +checksum = "c4dfe8371c9b2e126a8b64f6fefa54cef716ff2a50e63b5558a48b899265bccd" dependencies = [ "sentry-backtrace", "sentry-core", @@ -6560,9 +6285,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82eabcab0a047040befd44599a1da73d3adb228ff53b5ed9795ae04535577704" +checksum = "5aca8b88978677a27ee1a91beafe4052306c474c06f582321fde72d2e2cc2f7f" dependencies = [ "sentry-backtrace", "sentry-core", @@ -6572,19 +6297,19 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.31.8" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da956cca56e0101998c8688bc65ce1a96f00673a0e58e663664023d4c7911e82" +checksum = "9e7a88e0c1922d19b3efee12a8215f6a8a806e442e665ada71cc222cab72985f" dependencies = [ "debugid", + "getrandom", "hex", - "rand 0.8.5", "serde", "serde_json", "thiserror", "time", "url", - "uuid", + "uuid 1.4.1", ] [[package]] @@ -6598,9 +6323,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ "serde", ] @@ -6622,29 +6347,28 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 1.0.107", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "indexmap 2.5.0", + "indexmap 1.9.3", "itoa", - "memchr", "ryu", "serde", ] @@ -6699,18 +6423,30 @@ dependencies = [ "darling 0.13.4", "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] name = "sha-1" -version = "0.10.1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha-1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -6721,14 +6457,14 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] name = "sha1_smol" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" @@ -6738,14 +6474,14 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] name = "sharded-slab" -version = "0.1.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -6757,31 +6493,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "signal-hook-registry" -version = "1.4.2" +name = "signal-hook" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", + "signal-hook-registry", ] [[package]] -name = "signature" -version = "1.6.4" +name = "signal-hook-registry" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ - "digest", - "rand_core 0.6.4", + "libc", ] [[package]] name = "signature" -version = "2.2.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -6808,12 +6544,9 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg 1.3.0", -] +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "sluice" @@ -6821,7 +6554,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" dependencies = [ - "async-channel 1.9.0", + "async-channel 1.6.1", "futures-core", "futures-io", ] @@ -6834,9 +6567,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.10" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -6844,12 +6577,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -6877,16 +6610,6 @@ dependencies = [ "der 0.6.1", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.9", -] - [[package]] name = "stable-pattern" version = "0.1.0" @@ -6907,13 +6630,12 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.5" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" dependencies = [ "unicode-bidi", "unicode-normalization", - "unicode-properties", ] [[package]] @@ -6938,7 +6660,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -6960,9 +6682,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -6971,9 +6693,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -6988,7 +6710,7 @@ checksum = "b5dc35bb08dd1ca3dfb09dce91fd2d13294d6711c88897d9a9d60acf39bce049" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -7002,9 +6724,6 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] [[package]] name = "synom" @@ -7023,15 +6742,15 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 1.0.109", - "unicode-xid 0.2.5", + "syn 1.0.107", + "unicode-xid 0.2.3", ] [[package]] name = "sysinfo" -version = "0.27.8" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" +checksum = "c215311383d25d03753375c53ab9fad8fc0cf46953c409211e065edeabf577ee" dependencies = [ "cfg-if", "core-foundation-sys", @@ -7049,18 +6768,7 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys 0.5.0", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "system-configuration-sys 0.6.0", + "system-configuration-sys", ] [[package]] @@ -7073,16 +6781,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -7108,12 +6806,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "target-lexicon" version = "0.12.16" @@ -7122,22 +6814,22 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand 2.1.1", "once_cell", - "rustix 0.38.35", + "rustix", "windows-sys 0.59.0", ] [[package]] name = "termcolor" -version = "1.4.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] @@ -7159,16 +6851,15 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "cfg-if", "once_cell", ] @@ -7185,16 +6876,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "deranged", "itoa", "libc", - "num-conv", "num_threads", - "powerfmt", "serde", "time-core", "time-macros", @@ -7202,34 +6890,33 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ - "num-conv", "time-core", ] [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" @@ -7249,13 +6936,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", - "bytes 1.7.1", + "bytes 1.5.0", "libc", "mio", "parking_lot", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "signal-hook-registry", - "socket2 0.5.7", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.52.0", ] @@ -7268,14 +6955,14 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "tokio-native-tls" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", "tokio 1.40.0", @@ -7287,7 +6974,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.9", + "rustls 0.20.6", "tokio 1.40.0", "webpki", ] @@ -7313,47 +7000,37 @@ dependencies = [ "tokio 1.40.0", ] -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls 0.23.12", - "rustls-pki-types", - "tokio 1.40.0", -] - [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ - "bytes 1.7.1", + "bytes 1.5.0", "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", + "tracing", ] [[package]] name = "toml" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -7367,7 +7044,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -7379,39 +7056,28 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.5.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.0.1", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] [[package]] name = "totp-lite" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e43134db17199f7f721803383ac5854edd0d3d523cc34dba321d6acfbe76c3" +checksum = "5cc496875d9c8fe9a0ce19e3ee8e8808c60376831a439543f0aac71c9dd129fa" dependencies = [ - "digest", + "digest 0.10.7", "hmac", - "sha1", + "sha-1 0.10.0", "sha2", ] @@ -7424,7 +7090,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tokio 1.40.0", "tower-layer", "tower-service", @@ -7438,26 +7104,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.6.0", - "bytes 1.7.1", + "bytes 1.5.0", "http 1.1.0", - "http-body 1.0.1", + "http-body 1.0.0", "http-body-util", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" @@ -7466,7 +7132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", - "pin-project-lite 0.2.14", + "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", ] @@ -7479,7 +7145,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] @@ -7504,24 +7170,24 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.2.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ + "lazy_static", "log", - "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ + "ansi_term", + "lazy_static", "matchers", - "nu-ansi-term", - "once_cell", "regex", "sharded-slab", "smallvec", @@ -7584,24 +7250,24 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.5" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "byteorder", - "bytes 1.7.1", + "bytes 1.5.0", "http 0.2.12", "httparse", "log", "rand 0.8.5", - "sha-1", + "sha-1 0.10.0", "thiserror", "url", "utf-8", @@ -7615,7 +7281,7 @@ checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -7635,9 +7301,9 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "ulid" @@ -7682,9 +7348,9 @@ dependencies = [ [[package]] name = "uncased" -version = "0.9.10" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" dependencies = [ "serde", "version_check", @@ -7692,9 +7358,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ "version_check", ] @@ -7717,30 +7383,24 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-properties" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" - [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-xid" @@ -7750,9 +7410,9 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" @@ -7778,11 +7438,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.10.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" dependencies = [ - "base64 0.22.1", + "base64 0.21.3", "log", "native-tls", "once_cell", @@ -7791,9 +7451,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -7828,7 +7488,7 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.0.1", "serde", "serde_json", "utoipa-gen", @@ -7844,7 +7504,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "regex", - "syn 2.0.77", + "syn 2.0.76", "ulid 1.1.3", ] @@ -7862,9 +7522,19 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", "serde", @@ -7898,11 +7568,11 @@ dependencies = [ [[package]] name = "validator" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591" dependencies = [ - "idna 0.4.0", + "idna 0.2.3", "lazy_static", "regex", "serde", @@ -7924,7 +7594,7 @@ dependencies = [ "proc-macro2", "quote 1.0.37", "regex", - "syn 1.0.109", + "syn 1.0.107", "validator_types", ] @@ -7935,7 +7605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" dependencies = [ "proc-macro2", - "syn 1.0.109", + "syn 1.0.107", ] [[package]] @@ -7958,16 +7628,16 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "7.5.1" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21b881cd6636ece9735721cf03c1fe1e774fe258683d084bb2812ab67435749" +checksum = "571b69f690c855821462709b6f41d42ceccc316fbd17b60bd06d06928cfe6a99" dependencies = [ "anyhow", "cfg-if", "enum-iterator", "getset", "git2", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "rustversion", "sysinfo", "thiserror", @@ -7982,9 +7652,9 @@ checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vsimd" @@ -7994,16 +7664,17 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "waker-fn" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "want" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ + "log", "try-lock", ] @@ -8013,15 +7684,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasix" -version = "0.12.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" -dependencies = [ - "wasi", -] - [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -8044,15 +7706,15 @@ dependencies = [ "once_cell", "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if", "js-sys", @@ -8078,7 +7740,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8091,15 +7753,15 @@ checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-push" -version = "0.10.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49214685777c429ac44a92780983f4fadeecddd8c08c463d8196521e17e974bd" +checksum = "30bdf799b4be6f04bfee94807548718de568834c9ea020e73b59af3dac66d72c" dependencies = [ "async-trait", - "base64 0.13.1", + "base64 0.13.0", "chrono", "ece", - "futures-lite 1.13.0", + "futures-lite", "http 0.2.12", "isahc", "jwt-simple", @@ -8113,9 +7775,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -8143,28 +7805,28 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" dependencies = [ "webpki", ] [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] @@ -8175,11 +7837,20 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "widestring" -version = "1.1.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" [[package]] name = "winapi" @@ -8199,11 +7870,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "windows-sys 0.59.0", + "winapi", ] [[package]] @@ -8212,15 +7883,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-core" version = "0.52.0" @@ -8231,33 +7893,16 @@ dependencies = [ ] [[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" +name = "windows-sys" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -8330,6 +7975,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -8342,6 +7993,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -8360,6 +8017,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -8372,6 +8035,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -8396,6 +8065,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -8410,39 +8085,39 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] [[package]] -name = "winnow" -version = "0.6.18" +name = "winreg" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "memchr", + "winapi", ] [[package]] name = "winreg" -version = "0.50.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "winapi", ] [[package]] -name = "wyz" -version = "0.5.1" +name = "winreg" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ - "tap", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] @@ -8477,7 +8152,7 @@ checksum = "b61da40aeb0907a65f7fb5c1de83c5a224d6a9ebb83bf918588a2bb744d636b8" dependencies = [ "anyhow", "async-trait", - "base64 0.21.7", + "base64 0.21.3", "futures", "http 0.2.12", "hyper 0.14.30", @@ -8502,7 +8177,6 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive", ] @@ -8514,14 +8188,14 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.76", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" [[package]] name = "zune-core" @@ -8549,5 +8223,5 @@ dependencies = [ [[patch.unused]] name = "redis" -version = "0.22.3" -source = "git+https://github.com/revoltchat/redis-rs?rev=1a41faf356fd21aebba71cea7eb7eb2653e5f0ef#1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" +version = "0.23.1" +source = "git+https://github.com/revoltchat/redis-rs?rev=f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9#f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml index aed79b6a9..69341ba24 100644 --- a/crates/bonfire/Cargo.toml +++ b/crates/bonfire/Cargo.toml @@ -36,7 +36,7 @@ async-std = { version = "1.8.0", features = [ ] } # core -authifier = { version = "1.0.8" } +authifier = { version = "1.0.9" } revolt-result = { path = "../core/result" } revolt-models = { path = "../core/models" } revolt-config = { path = "../core/config" } diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 4cc64674f..630472bf8 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -84,11 +84,11 @@ axum = { version = "0.7.5", optional = true } # Rocket Impl schemars = { version = "0.8.8", optional = true } -rocket = { version = "0.5.0-rc.2", default-features = false, features = [ +rocket = { version = "0.5.1", default-features = false, features = [ "json", ], optional = true } revolt_okapi = { version = "0.9.1", optional = true } -revolt_rocket_okapi = { version = "0.9.1", optional = true } +revolt_rocket_okapi = { version = "0.10.0", optional = true } # Notifications fcm_v1 = "0.3.0" @@ -96,7 +96,7 @@ web-push = "0.10.0" revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } # Authifier -authifier = { version = "1.0.8" } +authifier = { version = "1.0.9" } # RabbitMQ amqprs = { version = "1.7.0" } diff --git a/crates/core/result/Cargo.toml b/crates/core/result/Cargo.toml index 742834fa6..edd6e8e61 100644 --- a/crates/core/result/Cargo.toml +++ b/crates/core/result/Cargo.toml @@ -29,7 +29,7 @@ utoipa = { version = "4.2.3", optional = true } # Rocket rocket = { optional = true, version = "0.5.0-rc.2", default-features = false } -revolt_rocket_okapi = { version = "0.9.1", optional = true } +revolt_rocket_okapi = { version = "0.10.0", optional = true } revolt_okapi = { version = "0.9.1", optional = true } # Axum diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index f07d08ab0..0ef3e749e 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -61,13 +61,13 @@ rocket_prometheus = "0.10.0-rc.3" # spec generation schemars = "0.8.8" -revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] } +revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] } # rabbit amqprs = { version = "1.7.0" } # core -authifier = "1.0.8" +authifier = "1.0.9" revolt-config = { path = "../core/config" } revolt-database = { path = "../core/database", features = [ "rocket-impl", From f6851ea1904a1f167d589c51632f745ac185f8c4 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 20 Oct 2024 17:16:25 -0700 Subject: [PATCH 23/44] add rocket_impl flag to authifier --- crates/core/database/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 630472bf8..0eb61e2f1 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -96,7 +96,7 @@ web-push = "0.10.0" revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } # Authifier -authifier = { version = "1.0.9" } +authifier = { version = "1.0.9", features = ["rocket_impl"] } # RabbitMQ amqprs = { version = "1.7.0" } From 424f98338c3bc509bccfc20ebbc4ded224898cf7 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 22 Oct 2024 09:58:55 -0700 Subject: [PATCH 24/44] make the tests file of delta actually compile --- crates/delta/src/util/test.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index f686c0183..467e945be 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -100,17 +100,15 @@ impl TestHarness { } let mut stream = self.sub.on_message(); - while let Some(underlying) = stream.next().await { - if let Ok(item) = underlying { - let msg_topic = item.get_channel_name(); - let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); + while let Some(item) = stream.next().await { + let msg_topic = item.get_channel_name(); + let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); - if topic == msg_topic && predicate(&payload) { - return payload; - } - - self.event_buffer.push((msg_topic.to_string(), payload)); + if topic == msg_topic && predicate(&payload) { + return payload; } + + self.event_buffer.push((msg_topic.to_string(), payload)); } // WARNING: if predicate is never satisfied, this will never return From 795e6afc26e81b92521716fdc536b57604f265fa Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Fri, 25 Oct 2024 10:02:56 -0700 Subject: [PATCH 25/44] fix: don't silence every push message --- Cargo.lock | 12 +++++------- Cargo.toml | 5 +++-- crates/core/database/src/models/messages/model.rs | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 588642a8d..00b7a67fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,9 +446,7 @@ dependencies = [ [[package]] name = "authifier" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30269caf0aaf1e1b542b150030e9688bf41d50026e09a51efd9408f332636c9d" +version = "1.0.9" dependencies = [ "async-std", "async-trait", @@ -467,7 +465,7 @@ dependencies = [ "regex", "reqwest 0.11.10", "revolt_okapi", - "revolt_rocket_okapi 0.9.1", + "revolt_rocket_okapi 0.10.0", "rocket", "rust-argon2", "schemars", @@ -5802,12 +5800,12 @@ dependencies = [ [[package]] name = "rocket_empty" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0922e47f981204fee38578a8efcf47a5c1a4ac0eb7f59e6bdfa3e61c8e3d69" +checksum = "97a55000e1ef5f4a9b20ae3d9de2a0bd22620c78ebd1aa568776ae12276125a6" dependencies = [ "revolt_okapi", - "revolt_rocket_okapi 0.9.1", + "revolt_rocket_okapi 0.10.0", "rocket", ] diff --git a/Cargo.toml b/Cargo.toml index dee195b96..134920f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,6 @@ members = [ # mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" } redis22 = { package = "redis", version = "0.22.3", git = "https://github.com/revoltchat/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" } redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/revoltchat/redis-rs", rev = "f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" } -# authifier = { package = "authifier", version = "1.0.8", path = "../authifier/crates/authifier" } -# rocket_authifier = { package = "rocket_authifier", version = "1.0.8", path = "../authifier/crates/rocket_authifier" } +authifier = { package = "authifier", version = "1.0.9", path = "../authifier/crates/authifier" } +#rocket_authifier = { package = "rocket_authifier", version = "1.0.9", path = "../authifier/crates/rocket_authifier" } +#rocket_empty = { package = "rocket_empty", version = "0.1.2", git = "https://github.com/IAmTomahawkx/rocket_empty", rev = "4be43b75ef740324a0cd7a1d9866e53ae1dc586e" } diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index dfa2a2b7c..1446bbfe2 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -493,7 +493,7 @@ impl Message { } _ => vec![], }, - true, + false, )], }, ) From c16da74484a79984af97469a0f6d6dbfe779a25f Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 28 Oct 2024 11:03:18 -0700 Subject: [PATCH 26/44] fix: don't silence all messages --- crates/core/database/src/models/messages/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 1446bbfe2..a0de80fc5 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -493,7 +493,7 @@ impl Message { } _ => vec![], }, - false, + self.has_suppressed_notifications(), )], }, ) From 346bc015fa548523426504b9b5cb7154ae7d72bc Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 28 Oct 2024 17:01:06 -0700 Subject: [PATCH 27/44] add debug logging for sending data to rabbit from message events --- crates/core/database/src/tasks/ack.rs | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index 62baf6739..c3f84110f 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -57,7 +57,11 @@ pub async fn queue_ack(channel: String, user: String, event: AckEvent) { }) .ok(); - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); + info!( + "Queue is using {} slots from {}. Queued type: ACK", + Q.len(), + Q.capacity() + ); } pub async fn queue_message(channel: String, event: AckEvent) { @@ -68,7 +72,11 @@ pub async fn queue_message(channel: String, event: AckEvent) { }) .ok(); - info!("Queue is using {} slots from {}.", Q.len(), Q.capacity()); + info!( + "Queue is using {} slots from {}. Queued type: MENTION", + Q.len(), + Q.capacity() + ); } pub async fn handle_ack_event( @@ -105,12 +113,19 @@ pub async fn handle_ack_event( } AckEvent::ProcessMessage { messages } => { let mut users: HashSet<&String> = HashSet::new(); + debug!( + "Processing {} messages from channel {}", + messages.len(), + messages[0].1.channel + ); // find all the users we'll be notifying messages .iter() .for_each(|(_, _, recipents, _)| users.extend(recipents.iter())); + debug!("Found {} users to notify.", users.len()); + for user in users { let message_ids: Vec = messages .iter() @@ -122,13 +137,25 @@ pub async fn handle_ack_event( db.add_mention_to_unread(channel, user, &message_ids) .await?; } + debug!("Added {} mentions for user {}", message_ids.len(), &user); } for (push, _, recipients, silenced) in messages { if *silenced || recipients.is_empty() || push.is_none() { + debug!( + "Rejecting push: silenced: {}, recipient count: {}, push exists: {:?}", + *silenced, + recipients.length(), + push + ); continue; } + debug!( + "Sending push event to AMQP; message {} for {} users", + push.as_ref().unwrap().message.id, + recipients.len() + ); if let Err(err) = amqp .message_sent(recipients.clone(), push.clone().unwrap()) .await @@ -162,6 +189,7 @@ pub async fn worker(db: Database, amqp: AMQP) { let (user, channel, _) = key; if let Err(err) = handle_ack_event(&event, &db, &amqp, user, channel).await { + revolt_config::capture_error(&err); error!("{err:?} for {event:?}. ({user:?}, {channel})"); } else { info!("User {user:?} ack in {channel} with {event:?}"); From 77bb7db02f2e708af973d8b7013a9b497a3a9038 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 28 Oct 2024 17:02:02 -0700 Subject: [PATCH 28/44] validate mentions at a server membership level --- .../database/src/models/messages/model.rs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index a0de80fc5..7ec194cef 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, hash::RandomState}; use indexmap::{IndexMap, IndexSet}; use iso8601_timestamp::Timestamp; @@ -7,7 +7,7 @@ use revolt_models::v0::{ self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageFlags, MessageSort, MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION, }; -use revolt_permissions::{ChannelPermission, PermissionValue}; +use revolt_permissions::ChannelPermission; use revolt_result::Result; use ulid::Ulid; use validator::Validate; @@ -338,6 +338,48 @@ impl Message { } } + // Validate the mentions go to users in the channel/server + if !mentions.is_empty() { + match channel { + Channel::DirectMessage { ref recipients, .. } + | Channel::Group { ref recipients, .. } => { + let recipients_hash: HashSet<&String, RandomState> = + HashSet::from_iter(recipients); + mentions.retain(|m| recipients_hash.contains(m)); + } + Channel::TextChannel { ref server, .. } + | Channel::VoiceChannel { ref server, .. } => { + let mentions_vec = Vec::from_iter(mentions.iter().cloned()); + + let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await; + if let Ok(valid_members) = valid_members { + let valid_mentions: HashSet<&String, RandomState> = + HashSet::from_iter(valid_members.iter().map(|m| &m.id.user)); + + mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server + + // Need to build a struct for bulk querying user permissions for a channel, + // as this would involve fetching all the requisite information (server permissions, channel permissions, etc) for every user. + // if !mentions.is_empty() { + // // if there are still mentions, drill down to a channel-level + // for member in valid_members.iter() { + // DatabasePermissionQuery::new(db, member.into()) + // .channel(&channel) + // .member(&member) + // . + // } + // } + } else { + revolt_config::capture_error(&valid_members.unwrap_err()); + return Err(create_error!(InternalError)); + } + } + Channel::SavedMessages { .. } => { + mentions.clear(); + } + } + } + if !mentions.is_empty() { message.mentions.replace(mentions.into_iter().collect()); } From 323fb60b410cbf45feb7ae1c2bc1fc23fd4f8076 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 28 Oct 2024 17:04:17 -0700 Subject: [PATCH 29/44] put back that import that was actually important --- crates/core/database/src/models/messages/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 7ec194cef..2758afba2 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -7,7 +7,7 @@ use revolt_models::v0::{ self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageFlags, MessageSort, MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION, }; -use revolt_permissions::ChannelPermission; +use revolt_permissions::{ChannelPermission, PermissionValue}; use revolt_result::Result; use ulid::Ulid; use validator::Validate; From 33d7376269526718df0c5ae9c1d56092bf473286 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Fri, 1 Nov 2024 14:30:56 -0700 Subject: [PATCH 30/44] minor fix to lockfile --- Cargo.lock | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cde0aa97a..fe742d8de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1657,15 +1657,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" -[[package]] -name = "ctr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "ctr" version = "0.9.2" @@ -5758,8 +5749,8 @@ dependencies = [ "scraper", "serde", "serde_json", - "tokio 1.40.0", "tempfile", + "tokio 1.40.0", "tracing", "tracing-subscriber", "utoipa", From 0954fb233682b1818fa86d7dead0ee2c1fc4ca56 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 4 Nov 2024 09:15:34 -0800 Subject: [PATCH 31/44] update delta authifier --- crates/delta/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index 29395af36..f44f09d2a 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -56,7 +56,7 @@ lettre = "0.10.0-alpha.4" rocket = { version = "0.5.1", default-features = false, features = ["json"] } rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" } rocket_empty = { version = "0.1.1", features = ["schema"] } -rocket_authifier = { version = "1.0.8" } +rocket_authifier = { version = "1.0.9" } rocket_prometheus = "0.10.0-rc.3" # spec generation From 0fda34c21e55e87de6d929ec2b19fa1312f1d9e6 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 23 Nov 2024 02:01:42 -0800 Subject: [PATCH 32/44] feat: proper permissions for push notifications --- Cargo.lock | 32 +- .../database/src/models/messages/model.rs | 26 +- .../database/src/util/bulk_permissions.rs | 331 ++++++++++++++++++ crates/core/database/src/util/mod.rs | 1 + crates/daemons/pushd/src/main.rs | 4 +- 5 files changed, 357 insertions(+), 37 deletions(-) create mode 100644 crates/core/database/src/util/bulk_permissions.rs diff --git a/Cargo.lock b/Cargo.lock index fe742d8de..529c77ee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,7 +466,7 @@ dependencies = [ "regex", "reqwest 0.11.10", "revolt_okapi", - "revolt_rocket_okapi 0.10.0", + "revolt_rocket_okapi", "rocket", "rust-argon2", "schemars", @@ -5646,7 +5646,7 @@ dependencies = [ "revolt_a2", "revolt_okapi", "revolt_optional_struct", - "revolt_rocket_okapi 0.10.0", + "revolt_rocket_okapi", "rocket", "schemars", "serde", @@ -5691,7 +5691,7 @@ dependencies = [ "revolt-permissions", "revolt-presence", "revolt-result", - "revolt_rocket_okapi 0.10.0", + "revolt_rocket_okapi", "rocket", "rocket_authifier", "rocket_cors", @@ -5844,7 +5844,7 @@ version = "0.7.18" dependencies = [ "axum", "revolt_okapi", - "revolt_rocket_okapi 0.10.0", + "revolt_rocket_okapi", "rocket", "schemars", "serde", @@ -5904,22 +5904,6 @@ dependencies = [ "syn 0.11.11", ] -[[package]] -name = "revolt_rocket_okapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "275e1e9bd3343f75225cafa64f4bfb939c8b21c5f861141180fc0e24769ff6cf" -dependencies = [ - "either", - "log", - "revolt_okapi", - "revolt_rocket_okapi_codegen", - "rocket", - "schemars", - "serde", - "serde_json", -] - [[package]] name = "revolt_rocket_okapi" version = "0.10.0" @@ -6061,14 +6045,14 @@ dependencies = [ [[package]] name = "rocket_authifier" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89a12311f60e9288833fc3ce6029bce5d5c61870ceef74d4a50668a8b520ad" +checksum = "810753b79106c44a4e76247fc7576b660663133a9e8f4b0afeb303589ec51d59" dependencies = [ "authifier", "iso8601-timestamp 0.1.10", "revolt_okapi", - "revolt_rocket_okapi 0.9.1", + "revolt_rocket_okapi", "rocket", "rocket_empty", "schemars", @@ -6115,7 +6099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a55000e1ef5f4a9b20ae3d9de2a0bd22620c78ebd1aa568776ae12276125a6" dependencies = [ "revolt_okapi", - "revolt_rocket_okapi 0.10.0", + "revolt_rocket_okapi", "rocket", ] diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index c4f42fb5a..61b6499ff 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -15,7 +15,7 @@ use validator::Validate; use crate::{ events::client::EventV1, tasks::{self, ack::AckEvent}, - util::idempotency::IdempotencyKey, + util::{bulk_permissions::BulkDatabasePermissionQuery, idempotency::IdempotencyKey}, Channel, Database, Emoji, File, User, AMQP, }; @@ -358,17 +358,19 @@ impl Message { mentions.retain(|m| valid_mentions.contains(m)); // quick pass, validate mentions are in the server - // Need to build a struct for bulk querying user permissions for a channel, - // as this would involve fetching all the requisite information (server permissions, channel permissions, etc) for every user. - // if !mentions.is_empty() { - // // if there are still mentions, drill down to a channel-level - // for member in valid_members.iter() { - // DatabasePermissionQuery::new(db, member.into()) - // .channel(&channel) - // .member(&member) - // . - // } - // } + if !mentions.is_empty() { + // if there are still mentions, drill down to a channel-level + let member_channel_view_perms = + BulkDatabasePermissionQuery::from_server_id(db, server) + .await + .channel(&channel) + .members(&valid_members) + .members_can_see_channel() + .await; + + mentions + .retain(|m| *member_channel_view_perms.get(m).unwrap_or(&false)); + } } else { revolt_config::capture_error(&valid_members.unwrap_err()); return Err(create_error!(InternalError)); diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs new file mode 100644 index 000000000..eb9bca475 --- /dev/null +++ b/crates/core/database/src/util/bulk_permissions.rs @@ -0,0 +1,331 @@ +use std::{collections::HashMap, hash::RandomState}; + +use revolt_permissions::{ + ChannelPermission, ChannelType, Override, OverrideField, PermissionValue, ALLOW_IN_TIMEOUT, + DEFAULT_PERMISSION_DIRECT_MESSAGE, +}; + +use crate::{Channel, Database, Member, Server, User}; + +#[derive(Clone)] +pub struct BulkDatabasePermissionQuery<'a> { + #[allow(dead_code)] + database: &'a Database, + + server: Server, + channel: Option, + users: Option>, + members: Option>, + + // In case the users or members are fetched as part of the permissions checking operation + pub(crate) cached_users: Option>, + pub(crate) cached_members: Option>, + + cached_member_perms: Option>, +} + +impl<'z, 'x> BulkDatabasePermissionQuery<'x> { + pub async fn members_can_see_channel(&'z mut self) -> HashMap + where + 'z: 'x, + { + let member_perms = if self.cached_member_perms.is_some() { + // This isn't done as an if let to prevent borrow checker errors with the mut self call when the perms aren't cached. + let perms = self.cached_member_perms.as_ref().unwrap(); + perms + .iter() + .map(|(m, p)| { + ( + m.clone(), + p.has_channel_permission(ChannelPermission::ViewChannel), + ) + }) + .collect() + } else { + calculate_members_permissions(self) + .await + .iter() + .map(|(m, p)| { + ( + m.clone(), + p.has_channel_permission(ChannelPermission::ViewChannel), + ) + }) + .collect() + }; + member_perms + } +} + +impl<'z> BulkDatabasePermissionQuery<'z> { + pub fn new(database: &Database, server: Server) -> BulkDatabasePermissionQuery<'_> { + BulkDatabasePermissionQuery { + database, + server, + channel: None, + users: None, + members: None, + cached_members: None, + cached_users: None, + cached_member_perms: None, + } + } + + pub async fn from_server_id<'a>( + db: &'a Database, + server: &str, + ) -> BulkDatabasePermissionQuery<'a> { + BulkDatabasePermissionQuery { + database: db, + server: db.fetch_server(server).await.unwrap(), + channel: None, + users: None, + members: None, + cached_members: None, + cached_users: None, + cached_member_perms: None, + } + } + + pub fn channel(self, channel: &'z Channel) -> BulkDatabasePermissionQuery { + BulkDatabasePermissionQuery { + channel: Some(channel.clone()), + ..self + } + } + + pub fn members(self, members: &'z [Member]) -> BulkDatabasePermissionQuery { + BulkDatabasePermissionQuery { + members: Some(members.to_owned()), + ..self + } + } + + pub fn users(self, users: &'z [User]) -> BulkDatabasePermissionQuery { + BulkDatabasePermissionQuery { + users: Some(users.to_owned()), + ..self + } + } + + /// Get the default channel permissions + /// Group channel defaults should be mapped to an allow-only override + #[allow(dead_code)] + async fn get_default_channel_permissions(&mut self) -> Override { + if let Some(channel) = &self.channel { + match channel { + Channel::Group { permissions, .. } => Override { + allow: permissions.unwrap_or(*DEFAULT_PERMISSION_DIRECT_MESSAGE as i64) as u64, + deny: 0, + }, + Channel::TextChannel { + default_permissions, + .. + } + | Channel::VoiceChannel { + default_permissions, + .. + } => default_permissions.unwrap_or_default().into(), + _ => Default::default(), + } + } else { + Default::default() + } + } + + #[allow(dead_code)] + fn get_channel_type(&mut self) -> ChannelType { + if let Some(channel) = &self.channel { + match channel { + Channel::DirectMessage { .. } => ChannelType::DirectMessage, + Channel::Group { .. } => ChannelType::Group, + Channel::SavedMessages { .. } => ChannelType::SavedMessages, + Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => { + ChannelType::ServerChannel + } + } + } else { + ChannelType::Unknown + } + } + + /// Get the ordered role overrides (from lowest to highest) for this member in this channel + #[allow(dead_code)] + async fn get_channel_role_overrides(&mut self) -> &HashMap { + if let Some(channel) = &self.channel { + match channel { + Channel::TextChannel { + role_permissions, .. + } + | Channel::VoiceChannel { + role_permissions, .. + } => role_permissions, + _ => panic!("Not supported for non-server channels"), + } + } else { + panic!("No channel added to query") + } + } +} + +/// Calculate members permissions in a server channel. +async fn calculate_members_permissions<'a>( + query: &'a mut BulkDatabasePermissionQuery<'a>, +) -> HashMap { + let mut resp = HashMap::new(); + + let (_, channel_role_permissions) = match query + .channel + .as_ref() + .expect("A channel must be assigned to calculate channel permissions") + .clone() + { + Channel::TextChannel { + id, + role_permissions, + .. + } + | Channel::VoiceChannel { + id, + role_permissions, + .. + } => (id, role_permissions), + _ => panic!("Calculation of member permissions must be done on a server channel"), + }; + + if query.users.is_none() { + let ids: Vec = query + .members + .as_ref() + .expect("No users or members added to the query") + .iter() + .map(|m| m.id.user.clone()) + .collect(); + + query.cached_users = Some( + query + .database + .fetch_users(&ids[..]) + .await + .expect("Failed to get data from the db"), + ); + + query.users = Some(query.cached_users.as_ref().unwrap().to_vec()) + } + + let users = query.users.as_ref().unwrap(); + + if query.members.is_none() { + let ids: Vec = query + .users + .as_ref() + .expect("No users or members added to the query") + .iter() + .map(|m| m.id.clone()) + .collect(); + + query.cached_members = Some( + query + .database + .fetch_members(&query.server.id, &ids[..]) + .await + .expect("Failed to get data from the db"), + ); + query.members = Some(query.cached_members.as_ref().unwrap().to_vec()) + } + + let members: HashMap<&String, &Member, RandomState> = HashMap::from_iter( + query + .members + .as_ref() + .unwrap() + .iter() + .map(|m| (&m.id.user, m)), + ); + + for user in users { + let member = members.get(&user.id); + + // User isn't a part of the server + if member.is_none() { + resp.insert(user.id.clone(), 0_u64.into()); + continue; + } + + let member = *member.unwrap(); + + if user.privileged { + resp.insert( + user.id.clone(), + PermissionValue::from(ChannelPermission::GrantAllSafe), + ); + continue; + } + + if user.id == query.server.owner { + resp.insert( + user.id.clone(), + PermissionValue::from(ChannelPermission::GrantAllSafe), + ); + continue; + } + + // Get the user's server permissions + let mut permission = calculate_server_permissions(&query.server, user, member); + + // Get the applicable role overrides + let mut roles = channel_role_permissions + .iter() + .filter(|(id, _)| member.roles.contains(id)) + .filter_map(|(id, permission)| { + query.server.roles.get(id).map(|role| { + let v: Override = (*permission).into(); + (role.rank, v) + }) + }) + .collect::>(); + + roles.sort_by(|a, b| b.0.cmp(&a.0)); + let overrides = roles.into_iter().map(|(_, v)| v); + + for role_override in overrides { + permission.apply(role_override) + } + + resp.insert(user.id.clone(), permission); + } + + resp +} + +/// Calculates a member's server permissions +fn calculate_server_permissions(server: &Server, user: &User, member: &Member) -> PermissionValue { + if user.privileged || server.owner == user.id { + return ChannelPermission::GrantAllSafe.into(); + } + + let mut permissions: PermissionValue = server.default_permissions.into(); + + let mut roles = server + .roles + .iter() + .filter(|(id, _)| member.roles.contains(id)) + .map(|(_, role)| { + let v: Override = role.permissions.into(); + (role.rank, v) + }) + .collect::>(); + + roles.sort_by(|a, b| b.0.cmp(&a.0)); + let role_overrides: Vec = roles.into_iter().map(|(_, v)| v).collect(); + + for role in role_overrides { + permissions.apply(role); + } + + if member.in_timeout() { + permissions.restrict(*ALLOW_IN_TIMEOUT); + } + + permissions +} diff --git a/crates/core/database/src/util/mod.rs b/crates/core/database/src/util/mod.rs index 26cf436e6..1baf7d8ba 100644 --- a/crates/core/database/src/util/mod.rs +++ b/crates/core/database/src/util/mod.rs @@ -1,4 +1,5 @@ pub mod bridge; +pub mod bulk_permissions; pub mod idempotency; pub mod permissions; pub mod reference; diff --git a/crates/daemons/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs index 3c9da6dc1..58ac14d10 100644 --- a/crates/daemons/pushd/src/main.rs +++ b/crates/daemons/pushd/src/main.rs @@ -203,7 +203,9 @@ where routing_key, )) .await - .unwrap(); + .expect( + "This probably means the revolt.notifications exchange does not exist in rabbitmq!", + ); let args = BasicConsumeArguments::new(queue_name, "") .manual_ack(false) From 53772948284775527cf8c24ef96b49e9c1249b1e Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sat, 23 Nov 2024 19:34:30 -0800 Subject: [PATCH 33/44] add unit test for mention sanitization --- compose.yml | 4 +- crates/core/config/Revolt.test.toml | 6 + .../database/src/util/bulk_permissions.rs | 10 +- crates/delta/src/main.rs | 4 +- .../delta/src/routes/channels/message_send.rs | 215 ++++++++++++++++++ crates/delta/src/util/test.rs | 20 +- 6 files changed, 251 insertions(+), 8 deletions(-) diff --git a/compose.yml b/compose.yml index 3e94d1adc..04b0b5e7e 100644 --- a/compose.yml +++ b/compose.yml @@ -3,13 +3,13 @@ services: redis: image: eqalpha/keydb ports: - - "14079:6379" + - "6379:6379" # MongoDB database: image: mongo ports: - - "14017:27017" + - "27017:27017" volumes: - ./.data/db:/data/db diff --git a/crates/core/config/Revolt.test.toml b/crates/core/config/Revolt.test.toml index 084a46ee0..7ac98ee26 100644 --- a/crates/core/config/Revolt.test.toml +++ b/crates/core/config/Revolt.test.toml @@ -1,3 +1,9 @@ [database] mongodb = "mongodb://localhost" redis = "redis://localhost/" + +[rabbit] +host = "127.0.0.1" +port = 5672 +username = "rabbituser" +password = "rabbitpass" diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs index eb9bca475..400359772 100644 --- a/crates/core/database/src/util/bulk_permissions.rs +++ b/crates/core/database/src/util/bulk_permissions.rs @@ -174,7 +174,7 @@ async fn calculate_members_permissions<'a>( ) -> HashMap { let mut resp = HashMap::new(); - let (_, channel_role_permissions) = match query + let (_, channel_role_permissions, channel_default_permissions) = match query .channel .as_ref() .expect("A channel must be assigned to calculate channel permissions") @@ -183,13 +183,15 @@ async fn calculate_members_permissions<'a>( Channel::TextChannel { id, role_permissions, + default_permissions, .. } | Channel::VoiceChannel { id, role_permissions, + default_permissions, .. - } => (id, role_permissions), + } => (id, role_permissions, default_permissions), _ => panic!("Calculation of member permissions must be done on a server channel"), }; @@ -273,6 +275,10 @@ async fn calculate_members_permissions<'a>( // Get the user's server permissions let mut permission = calculate_server_permissions(&query.server, user, member); + if let Some(defaults) = channel_default_permissions { + permission.apply(defaults.into()); + } + // Get the applicable role overrides let mut roles = channel_role_permissions .iter() diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 5f737e4a4..478a212f6 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -10,7 +10,7 @@ pub mod util; use revolt_config::config; use revolt_database::events::client::EventV1; -use revolt_database::{Database, MongoDb, AMQP}; +use revolt_database::AMQP; use rocket::{Build, Rocket}; use rocket_cors::{AllowedOrigins, CorsOptions}; use rocket_prometheus::PrometheusMetrics; @@ -34,7 +34,7 @@ pub async fn web() -> Rocket { db.migrate_database().await.unwrap(); // Setup Authifier event channel - let (sender, receiver) = unbounded(); + let (_, receiver) = unbounded(); // Setup Authifier let authifier = db.clone().to_authifier().await; diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index ab3ba5582..55c133088 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -109,3 +109,218 @@ pub async fn message_send( .into_model(Some(model_user), model_member), )) } + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use crate::{rocket, util::test::TestHarness}; + use revolt_database::{ + util::{idempotency::IdempotencyKey, reference::Reference}, + Channel, Member, Message, PartialChannel, PartialMember, Role, Server, + }; + use revolt_models::v0::{self, DataCreateServerChannel}; + use revolt_permissions::{ChannelPermission, OverrideField}; + + #[rocket::async_test] + async fn message_mention_constraints() { + let harness = TestHarness::new().await; + let (_, _, user) = harness.new_user().await; + let (_, _, second_user) = harness.new_user().await; + + let (server, channels) = Server::create( + &harness.db, + v0::DataCreateServer { + name: "Test Server".to_string(), + ..Default::default() + }, + &user, + true, + ) + .await + .expect("Failed to create test server"); + + let server_mut: &mut Server = &mut server.clone(); + let mut locked_channel = Channel::create_server_channel( + &harness.db, + server_mut, + DataCreateServerChannel { + channel_type: v0::LegacyServerChannelType::Text, + name: "Hidden Channel".to_string(), + description: None, + nsfw: Some(false), + }, + true, + ) + .await + .expect("Failed to make new channel"); + + let role = Role { + name: "Show Hidden Channel".to_string(), + permissions: OverrideField { a: 0, d: 0 }, + colour: None, + hoist: false, + rank: 5, + }; + + let role_id = role + .create(&harness.db, &server.id) + .await + .expect("Failed to create the role"); + + let mut overrides = HashMap::new(); + overrides.insert( + role_id.clone(), + OverrideField { + a: (ChannelPermission::ViewChannel) as i64, + d: 0, + }, + ); + + let partial = PartialChannel { + name: None, + owner: None, + description: None, + icon: None, + nsfw: None, + active: None, + permissions: None, + role_permissions: Some(overrides), + default_permissions: Some(OverrideField { + a: 0, + d: ChannelPermission::ViewChannel as i64, + }), + last_message_id: None, + }; + locked_channel + .update(&harness.db, partial, vec![]) + .await + .expect("Failed to update the channel permissions for special role"); + + Member::create(&harness.db, &server, &user, Some(channels.clone())) + .await + .expect("Failed to create member"); + let member = Reference::from_unchecked(user.id.clone()) + .as_member(&harness.db, &server.id) + .await + .expect("Failed to get member"); + + // Second user is not part of the server + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("0".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention should not go through here + assert!( + message.mentions.is_none() || message.mentions.unwrap().is_empty(), + "Mention failed to be scrubbed when the user is not part of the server" + ); + + Member::create(&harness.db, &server, &second_user, Some(channels.clone())) + .await + .expect("Failed to create second member"); + let mut second_member = Reference::from_unchecked(second_user.id.clone()) + .as_member(&harness.db, &server.id) + .await + .expect("Failed to get second member"); + + // Second user cannot see the channel + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("1".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention should not go through here + assert!( + message.mentions.is_none() || message.mentions.unwrap().is_empty(), + "Mention failed to be scrubbed when the user cannot see the channel" + ); + + let second_member_roles = vec![role_id.clone()]; + let partial = PartialMember { + id: None, + joined_at: None, + nickname: None, + avatar: None, + timeout: None, + roles: Some(second_member_roles), + }; + second_member + .update(&harness.db, partial, vec![]) + .await + .expect("Failed to update the second user's roles"); + + // This time the mention SHOULD go through + let message = Message::create_from_api( + &harness.db, + Some(&harness.amqp), + locked_channel.clone(), + v0::DataMessageSend { + content: Some(format!("<@{}>", second_user.id)), + nonce: None, + attachments: None, + replies: None, + embeds: None, + masquerade: None, + interactions: None, + flags: None, + }, + v0::MessageAuthor::User(&user.clone().into(&harness.db, Some(&user)).await), + Some(user.clone().into(&harness.db, Some(&user)).await), + Some(member.clone().into()), + user.limits().await, + IdempotencyKey::unchecked_from_string("2".to_string()), + false, + true, + ) + .await + .expect("Failed to create message"); + + // The mention SHOULD go through here + assert!( + message.mentions.is_some() && !message.mentions.unwrap().is_empty(), + "Mention was scrubbed when the user can see the channel" + ); + } +} diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 467e945be..9b3f80b86 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -5,7 +5,7 @@ use authifier::{ use futures::StreamExt; use rand::Rng; use redis_kiss::redis::aio::PubSub; -use revolt_database::{events::client::EventV1, Database, User}; +use revolt_database::{events::client::EventV1, Database, User, AMQP}; use revolt_models::v0; use rocket::local::asynchronous::Client; @@ -13,6 +13,7 @@ pub struct TestHarness { pub client: Client, authifier: Authifier, pub db: Database, + pub amqp: AMQP, sub: PubSub, event_buffer: Vec<(String, EventV1)>, } @@ -20,7 +21,7 @@ pub struct TestHarness { impl TestHarness { pub async fn new() -> TestHarness { dotenv::dotenv().ok(); - + let config = revolt_config::config().await; let client = Client::tracked(crate::web().await) .await .expect("valid rocket instance"); @@ -43,10 +44,25 @@ impl TestHarness { .expect("`Authifier`") .clone(); + let connection = amqprs::connection::Connection::open( + &amqprs::connection::OpenConnectionArguments::new( + &config.rabbit.host, + config.rabbit.port, + &config.rabbit.username, + &config.rabbit.password, + ), + ) + .await + .unwrap(); + let channel = connection.open_channel(None).await.unwrap(); + + let amqp = AMQP::new(connection, channel); + TestHarness { client, authifier, db, + amqp, sub, event_buffer: vec![], } From 7d8bad45cc5e6cc3468ff78ae27fbd89d2a7ecac Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 09:42:01 -0800 Subject: [PATCH 34/44] remove local file dependancy on authifier --- Cargo.lock | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 529c77ee5..25ac46d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,6 +448,8 @@ dependencies = [ [[package]] name = "authifier" version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba4df3b5df5cf1a08d4af71c407fb56a675b6aaf4d1fec704da32595497d73d" dependencies = [ "async-std", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 134920f71..f99811437 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,6 @@ members = [ # mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" } redis22 = { package = "redis", version = "0.22.3", git = "https://github.com/revoltchat/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" } redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/revoltchat/redis-rs", rev = "f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" } -authifier = { package = "authifier", version = "1.0.9", path = "../authifier/crates/authifier" } +#authifier = { package = "authifier", version = "1.0.9", path = "../authifier/crates/authifier" } #rocket_authifier = { package = "rocket_authifier", version = "1.0.9", path = "../authifier/crates/rocket_authifier" } #rocket_empty = { package = "rocket_empty", version = "0.1.2", git = "https://github.com/IAmTomahawkx/rocket_empty", rev = "4be43b75ef740324a0cd7a1d9866e53ae1dc586e" } From 9ea5da8f1fe28ef1472ffb982653d82b73dd878a Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 10:25:03 -0800 Subject: [PATCH 35/44] update ports to proper defaults --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fd3f10d9..ea8cab84b 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ As a heads-up, the development environment uses the following ports: | Service | Port | | ------------------------- | :------------: | -| MongoDB | 14017 | -| Redis | 14079 | +| MongoDB | 27017 | +| Redis | 6379 | | MinIO | 14009 | | Maildev | 14025
14080 | | Revolt Web App | 14701 | From 0643a389e7378375bdc16956c7d188df846c5b82 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 12:24:27 -0800 Subject: [PATCH 36/44] fixTM the node bindings --- crates/bindings/node/src/lib.rs | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/bindings/node/src/lib.rs b/crates/bindings/node/src/lib.rs index a2030d73c..85b391834 100644 --- a/crates/bindings/node/src/lib.rs +++ b/crates/bindings/node/src/lib.rs @@ -7,25 +7,25 @@ use neon::prelude::*; use revolt_database::{Database, DatabaseInfo}; fn js_init(mut cx: FunctionContext) -> JsResult { - static INIT: OnceLock<()> = OnceLock::new(); - if INIT.get().is_none() { - INIT.get_or_init(|| { - async_std::task::block_on(async { - revolt_config::configure!(api); - - match DatabaseInfo::Auto.connect().await { - Ok(db) => { - let authifier_db = db.clone().to_authifier().await.database; - revolt_database::tasks::start_workers(db, authifier_db); - Ok(()) - } - Err(err) => Err(err), - } - }) - .or_else(|err| cx.throw_error(err)) - .unwrap(); - }); - } + // static INIT: OnceLock<()> = OnceLock::new(); + // if INIT.get().is_none() { + // INIT.get_or_init(|| { + // async_std::task::block_on(async { + // revolt_config::configure!(api); + + // match DatabaseInfo::Auto.connect().await { + // Ok(db) => { + // let authifier_db = db.clone().to_authifier().await.database; + // revolt_database::tasks::start_workers(db, authifier_db); + // Ok(()) + // } + // Err(err) => Err(err), + // } + // }) + // .or_else(|err| cx.throw_error(err)) + // .unwrap(); + // }); + // } Ok(cx.undefined()) } From 70274bf73cddbe79bbc758c9e793d8e1f2f37e85 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 16:18:16 -0800 Subject: [PATCH 37/44] Theoretically configure docker releases for pushd Signed-off-by: IAmTomahawkx --- .github/workflows/docker.yaml | 6 +++++- crates/daemons/pushd/Dockerfile | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 crates/daemons/pushd/Dockerfile diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 0d5a76042..58e186d0b 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -61,7 +61,7 @@ jobs: if: github.event_name != 'pull_request' strategy: matrix: - project: [delta, bonfire, autumn, january] + project: [delta, bonfire, autumn, january, pushd] name: Build ${{ matrix.project }} image steps: # Configure build environment @@ -106,6 +106,10 @@ jobs: "january": { "path": "crates/services/january", "tag": "${{ github.repository_owner }}/january" + }, + "pushd": { + "path": "crates/daemons/pushd", + "tag": "${{ github.repository_owner }}/pushd" } } export_to: output diff --git a/crates/daemons/pushd/Dockerfile b/crates/daemons/pushd/Dockerfile new file mode 100644 index 000000000..a1f80a39c --- /dev/null +++ b/crates/daemons/pushd/Dockerfile @@ -0,0 +1,9 @@ +# Build Stage +FROM ghcr.io/revoltchat/base:latest AS builder + +# Bundle Stage +FROM gcr.io/distroless/cc-debian12:nonroot +COPY --from=builder /home/rust/src/target/release/revolt-pushd ./ + +USER nonroot +CMD ["./revolt-pushd"] \ No newline at end of file From 2f8506fe7ae5afd0806f20df2968febe21869f90 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 16:38:58 -0800 Subject: [PATCH 38/44] declare exchange in pushd and delta --- crates/core/database/src/amqp/mod.rs | 1 + crates/daemons/pushd/src/main.rs | 14 +++++++++++++- crates/delta/src/main.rs | 14 +++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/core/database/src/amqp/mod.rs b/crates/core/database/src/amqp/mod.rs index 3c5bbbc73..9ed09d345 100644 --- a/crates/core/database/src/amqp/mod.rs +++ b/crates/core/database/src/amqp/mod.rs @@ -1 +1,2 @@ +#[allow(clippy::module_inception)] pub mod amqp; diff --git a/crates/daemons/pushd/src/main.rs b/crates/daemons/pushd/src/main.rs index 58ac14d10..c63b8d9a1 100644 --- a/crates/daemons/pushd/src/main.rs +++ b/crates/daemons/pushd/src/main.rs @@ -1,5 +1,8 @@ use amqprs::{ - channel::{BasicConsumeArguments, Channel, QueueBindArguments, QueueDeclareArguments}, + channel::{ + BasicConsumeArguments, Channel, ExchangeDeclareArguments, QueueBindArguments, + QueueDeclareArguments, + }, connection::{Connection, OpenConnectionArguments}, consumer::AsyncConsumer, FieldTable, @@ -176,6 +179,15 @@ where let channel = connection.open_channel(None).await.unwrap(); + channel + .exchange_declare( + ExchangeDeclareArguments::new(&config.pushd.exchange, "direct") + .durable(true) + .finish(), + ) + .await + .expect("Failed to declare pushd exchange"); + let mut queue_name = queue_name.to_string(); if config.pushd.production { diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 478a212f6..c25cf7fa8 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -17,7 +17,10 @@ use rocket_prometheus::PrometheusMetrics; use std::net::Ipv4Addr; use std::str::FromStr; -use amqprs::connection::{Connection, OpenConnectionArguments}; +use amqprs::{ + channel::ExchangeDeclareArguments, + connection::{Connection, OpenConnectionArguments}, +}; use async_std::channel::unbounded; use authifier::AuthifierEvent; use rocket::data::ToByteUnit; @@ -89,6 +92,15 @@ pub async fn web() -> Rocket { .unwrap(); let channel = connection.open_channel(None).await.unwrap(); + channel + .exchange_declare( + ExchangeDeclareArguments::new(&config.pushd.exchange, "direct") + .durable(true) + .finish(), + ) + .await + .expect("Failed to declare exchange"); + let amqp = AMQP::new(connection, channel); // Launch background task workers From c2a0efe7f3253a901389408406a7aaa66d55df10 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Sun, 24 Nov 2024 17:37:36 -0800 Subject: [PATCH 39/44] fix createbuckets script Signed-off-by: IAmTomahawkx --- compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index dc7cd44d9..31af45bad 100644 --- a/compose.yml +++ b/compose.yml @@ -33,10 +33,10 @@ services: depends_on: - minio entrypoint: > - " /bin/sh -c while ! /usr/bin/mc ready minio; do + /bin/sh -c "while ! /usr/bin/mc ready minio; do /usr/bin/mc config host add minio http://minio:9000 minioautumn minioautumn; echo 'Waiting minio...' && sleep 1; - done; /usr/bin/mc mb minio/revolt-uploads; exit 0; " + done; /usr/bin/mc mb minio/revolt-uploads; exit 0;" # Rabbit rabbit: From 77aafd0e20b6ff95d29f86ce36f99972958a29ea Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Mon, 25 Nov 2024 19:09:09 -0800 Subject: [PATCH 40/44] fix: reference db implementation Signed-off-by: IAmTomahawkx --- .../database/src/models/server_members/ops/reference.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/core/database/src/models/server_members/ops/reference.rs b/crates/core/database/src/models/server_members/ops/reference.rs index 88c9c7b25..f038477ec 100644 --- a/crates/core/database/src/models/server_members/ops/reference.rs +++ b/crates/core/database/src/models/server_members/ops/reference.rs @@ -53,17 +53,17 @@ impl AbstractServerMembers for ReferenceDb { /// Fetch multiple members by their ids async fn fetch_members<'a>(&self, server_id: &str, ids: &'a [String]) -> Result> { let server_members = self.server_members.lock().await; - ids.iter() - .map(|id| { + Ok(ids + .iter() + .filter_map(|id| { server_members .get(&MemberCompositeKey { server: server_id.to_string(), user: id.to_string(), }) .cloned() - .ok_or_else(|| create_error!(NotFound)) }) - .collect() + .collect()) } /// Fetch member count of a server From 7b73fc365ea50053265e5d4bc874413645f0251f Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Tue, 26 Nov 2024 08:40:10 -0800 Subject: [PATCH 41/44] fix: remove finally redundant code Signed-off-by: IAmTomahawkx --- .../database/src/models/messages/model.rs | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index 61b6499ff..725d46d1d 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -383,34 +383,7 @@ impl Message { } if !mentions.is_empty() { - // FIXME: temp fix to stop spam attacks - match channel { - Channel::DirectMessage { ref recipients, .. } - | Channel::Group { ref recipients, .. } => { - let recipients_hash: HashSet<&String, RandomState> = - HashSet::from_iter(recipients.iter()); - - mentions.retain(|m| recipients_hash.contains(m)); - } - Channel::TextChannel { ref server, .. } - | Channel::VoiceChannel { ref server, .. } => { - let mentions_vec = Vec::from_iter(mentions.iter().cloned()); - let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await; - if let Ok(valid_members) = valid_members { - let valid_ids: HashSet = HashSet::from_iter( - valid_members.iter().map(|member| member.id.user.clone()), - ); - mentions.retain(|m| valid_ids.contains(m)); - } else { - revolt_config::capture_error(&valid_members.unwrap_err()); - } - } - Channel::SavedMessages { .. } => mentions.clear(), - } - - if !mentions.is_empty() { - message.mentions.replace(mentions.into_iter().collect()); - } + message.mentions.replace(mentions.into_iter().collect()); } if !replies.is_empty() { From 63c7494e2cdfc690a5ce0119e993f5354073be29 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 27 Nov 2024 10:07:54 -0800 Subject: [PATCH 42/44] fix: changes Signed-off-by: IAmTomahawkx --- Cargo.toml | 4 ---- crates/core/database/src/tasks/ack.rs | 4 ++-- crates/daemons/pushd/src/consumers/base.rs | 0 crates/daemons/pushd/src/consumers/inbound/ack.rs | 15 +++------------ 4 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 crates/daemons/pushd/src/consumers/base.rs diff --git a/Cargo.toml b/Cargo.toml index f99811437..2779c26bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,5 @@ members = [ ] [patch.crates-io] -# mobc-redis = { git = "https://github.com/insertish/mobc", rev = "8b880bb59f2ba80b4c7bc40c649c113d8857a186" } redis22 = { package = "redis", version = "0.22.3", git = "https://github.com/revoltchat/redis-rs", rev = "1a41faf356fd21aebba71cea7eb7eb2653e5f0ef" } redis23 = { package = "redis", version = "0.23.1", git = "https://github.com/revoltchat/redis-rs", rev = "f8ca28ab85da59d2ccde526b4d2fb390eff5a5f9" } -#authifier = { package = "authifier", version = "1.0.9", path = "../authifier/crates/authifier" } -#rocket_authifier = { package = "rocket_authifier", version = "1.0.9", path = "../authifier/crates/rocket_authifier" } -#rocket_empty = { package = "rocket_empty", version = "0.1.2", git = "https://github.com/IAmTomahawkx/rocket_empty", rev = "4be43b75ef740324a0cd7a1d9866e53ae1dc586e" } diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs index c3f84110f..89971f661 100644 --- a/crates/core/database/src/tasks/ack.rs +++ b/crates/core/database/src/tasks/ack.rs @@ -89,8 +89,8 @@ pub async fn handle_ack_event( match &event { #[allow(clippy::disallowed_methods)] // event is sent by higher level function AckEvent::AckMessage { id } => { - let _u = user.as_ref().unwrap(); - let user: &str = _u.as_str(); + let user = user.as_ref().unwrap(); + let user: &str = user.as_str(); let unread = db.fetch_unread(user, channel).await?; let updated = db.acknowledge_message(channel, user, id).await?; diff --git a/crates/daemons/pushd/src/consumers/base.rs b/crates/daemons/pushd/src/consumers/base.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/daemons/pushd/src/consumers/inbound/ack.rs b/crates/daemons/pushd/src/consumers/inbound/ack.rs index 2a46ebf8e..b9fe08861 100644 --- a/crates/daemons/pushd/src/consumers/inbound/ack.rs +++ b/crates/daemons/pushd/src/consumers/inbound/ack.rs @@ -67,12 +67,9 @@ impl AsyncConsumer for AckConsumer { let content = String::from_utf8(content).unwrap(); let payload: AckPayload = serde_json::from_str(content.as_str()).unwrap(); - log::debug!("Received Ack event"); - // Step 1: fetch unreads and don't continue if there's no unreads #[allow(clippy::disallowed_methods)] let unreads = self.db.fetch_unread_mentions(&payload.user_id).await; - println!("unreads: {:?}", unreads); if let Ok(u) = &unreads { if u.is_empty() { @@ -101,16 +98,12 @@ impl AsyncConsumer for AckConsumer { return; } - println!("sessions: {:?}", apple_sessions); - // Step 3: calculate the actual mention count, since we have to send it out let mut mention_count = 0; for u in &unreads.unwrap() { mention_count += u.mentions.as_ref().unwrap().len() } - println!("mention count: {}", mention_count); - // Step 4: loop through each apple session and send the badge update for session in apple_sessions { let service_payload = PayloadToService { @@ -129,19 +122,17 @@ impl AsyncConsumer for AckConsumer { ) .finish(); - println!( + log::debug!( "Publishing ack to apn session {}", session.subscription.as_ref().unwrap().auth ); publish_message(self, p.into(), args).await; } else { - println!("Failed to serialize ack badge update payload!"); - println!("{:?}", raw_service_payload.unwrap_err()) + log::warn!("Failed to serialize ack badge update payload!"); + revolt_config::capture_error(&raw_service_payload.unwrap_err()); } } - - println!("Done!"); } } } From 457683ea9be4b08fbdfd47e6d456be375ab798df Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Wed, 27 Nov 2024 10:10:02 -0800 Subject: [PATCH 43/44] fix: other changes Signed-off-by: IAmTomahawkx --- .../pushd/src/consumers/inbound/internal.rs | 10 ---------- .../pushd/src/consumers/outbound/vapid.rs | 19 ------------------- 2 files changed, 29 deletions(-) diff --git a/crates/daemons/pushd/src/consumers/inbound/internal.rs b/crates/daemons/pushd/src/consumers/inbound/internal.rs index 90430932c..387c08b52 100644 --- a/crates/daemons/pushd/src/consumers/inbound/internal.rs +++ b/crates/daemons/pushd/src/consumers/inbound/internal.rs @@ -43,19 +43,9 @@ pub(crate) async fn publish_message( } if let Some(chnl) = channel { - //if let Err(err) = chnl.basic_publish(BasicProperties::default(), payload.clone(), args.clone()) .await .unwrap(); - // { - // match err { - // Error::InternalChannelError(_) => { - // self.make_channel().await; - // self.publish_message(payload, args, attempt + 1).await; - // } - // _ => {} - // } - // } debug!("Sent message to queue for target {}", routing_key); } else { warn!("Failed to unwrap channel (including attempt to make a channel)!") diff --git a/crates/daemons/pushd/src/consumers/outbound/vapid.rs b/crates/daemons/pushd/src/consumers/outbound/vapid.rs index 1d386c1a4..fb735d5eb 100644 --- a/crates/daemons/pushd/src/consumers/outbound/vapid.rs +++ b/crates/daemons/pushd/src/consumers/outbound/vapid.rs @@ -20,25 +20,6 @@ pub struct VapidOutboundConsumer { pkey: Vec, } -// impl VapidOutboundConsumer { -// fn format_title(&self, notification: &PushNotification) -> String { -// // ideally this changes depending on context -// // in a server, it would look like "Sendername, #channelname in servername" -// // in a group, it would look like "Sendername in groupname" -// // in a dm it should just be "Sendername". -// // not sure how feasible all those are given the PushNotification object as it currently stands. - -// match ¬ification.channel { -// Channel::DirectMessage { .. } => notification.author.clone(), -// Channel::Group { name, .. } => format!("{}, #{}", notification.author, name), -// Channel::TextChannel { name, .. } | Channel::VoiceChannel { name, .. } => { -// format!("{} in #{}", notification.author, name) -// } -// _ => "Unknown".to_string(), -// } -// } -// } - impl VapidOutboundConsumer { pub async fn new(db: Database) -> Result { let config = revolt_config::config().await; From bdf772879f3250d4286dde07a72381fc45f60580 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Thu, 28 Nov 2024 00:08:50 -0800 Subject: [PATCH 44/44] fix: make channel name return result Signed-off-by: IAmTomahawkx --- crates/core/models/src/v0/channels.rs | 12 ++++++++---- crates/daemons/pushd/src/consumers/outbound/apn.rs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs index e3adca9e5..154ca48ca 100644 --- a/crates/core/models/src/v0/channels.rs +++ b/crates/core/models/src/v0/channels.rs @@ -307,13 +307,17 @@ impl Channel { } } - pub fn name(&self) -> &str { + /// This returns a Result because the recipient name can't be determined here without a db call, + /// which can't be done since this is models, which can't reference the database crate. + /// + /// If it returns Err, you need to fetch the name from the db. + pub fn name(&self) -> Result<&str, ()> { match self { - Channel::DirectMessage { .. } => "DMs", - Channel::SavedMessages { .. } => "Saved Messages", + Channel::DirectMessage { .. } => Err(()), + Channel::SavedMessages { .. } => Ok("Saved Messages"), Channel::TextChannel { name, .. } | Channel::Group { name, .. } - | Channel::VoiceChannel { name, .. } => name, + | Channel::VoiceChannel { name, .. } => Ok(name), } } } diff --git a/crates/daemons/pushd/src/consumers/outbound/apn.rs b/crates/daemons/pushd/src/consumers/outbound/apn.rs index 923ec8af3..3b6ac3a71 100644 --- a/crates/daemons/pushd/src/consumers/outbound/apn.rs +++ b/crates/daemons/pushd/src/consumers/outbound/apn.rs @@ -291,7 +291,7 @@ impl AsyncConsumer for ApnsOutboundConsumer { url: &alert.url, author_avatar: &alert.icon, author_display_name: &alert.author, - channel_name: alert.channel.name(), + channel_name: alert.channel.name().unwrap_or(&title), }; resp = self.client.send(apn_payload).await;