diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 4c4c3f5..137e166 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v3 - name: Install nix - uses: cachix/install-nix-action@v16 + uses: cachix/install-nix-action@v17 with: extra_nix_config: | experimental-features = nix-command flakes diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c2467e4..9ec2955 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,7 +33,7 @@ jobs: submodules: true - name: Install nix - uses: cachix/install-nix-action@v16 + uses: cachix/install-nix-action@v17 with: extra_nix_config: | experimental-features = nix-command flakes @@ -76,7 +76,7 @@ jobs: submodules: true - name: Install nix - uses: cachix/install-nix-action@v16 + uses: cachix/install-nix-action@v17 with: extra_nix_config: | experimental-features = nix-command flakes diff --git a/.gitmodules b/.gitmodules index e3152d6..33411f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "protocols/before_account_kind"] path = protocols/before_account_kind url = https://github.com/harmony-development/protocol.git +[submodule "protocols/before_proto_v2"] + path = protocols/before_proto_v2 + url = https://github.com/harmony-development/protocol.git diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..2723aa9 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +image: yusdacra/gitpod-workspace-base-nix:latest +tasks: + - command: nix develop diff --git a/Cargo.lock b/Cargo.lock index 59b5fb2..f6ab91b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.53" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "arc-swap" @@ -57,9 +57,9 @@ checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" [[package]] name = "argon2" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7" +checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f" dependencies = [ "base64ct", "blake2", @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -145,15 +145,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -177,15 +177,58 @@ checksum = "f9f65e4fb35ff6a80b3298d1f028649f3a23da141fa3951e9b24dde1d515b67e" [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4af7447fc1214c1f3a1ace861d0216a6c8bb13965b64bbad9650f375b67689a" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "3bdc19781b16e32f8a7200368a336fa4509d4b72ef15dd4e41df5290855ee1e6" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", +] [[package]] name = "axum-server" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cfd9dbe28ebde5c0460067ea27c6f3b1d514b699c4e0a5aab0fb63e452a8a8" +checksum = "abf18303ef7e23b045301555bf8a0dfbc1444ea1a37b3c81757a32680ace4d7d" dependencies = [ "arc-swap", "bytes", @@ -194,10 +237,10 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "rustls 0.20.2", - "rustls-pemfile", + "rustls 0.20.4", + "rustls-pemfile 1.0.0", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", "tower-service", ] @@ -209,9 +252,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" [[package]] name = "bit_field" @@ -238,27 +281,18 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08f9f6871a8eacbb960d18db3d077ae6db1f0bc0df3272a78ca09eef8c5a931" -dependencies = [ - "digest 0.10.3", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "generic-array", + "digest", ] [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -292,9 +326,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.7.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" [[package]] name = "byteorder" @@ -316,9 +350,9 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -339,6 +373,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -356,26 +399,27 @@ dependencies = [ [[package]] name = "console-api" -version = "0.1.1" -source = "git+https://github.com/tokio-rs/console.git?branch=main#884f4ecac8cba7eee7f895024da4c6e28de75289" +version = "0.2.0" +source = "git+https://github.com/tokio-rs/console.git?branch=main#d91ecdf7d85443873c0b903b1dd0ca8ae24ec54a" dependencies = [ "prost", "prost-types", "tonic", - "tonic-build", "tracing-core", ] [[package]] name = "console-subscriber" -version = "0.1.1" -source = "git+https://github.com/tokio-rs/console.git?branch=main#884f4ecac8cba7eee7f895024da4c6e28de75289" +version = "0.1.5" +source = "git+https://github.com/tokio-rs/console.git?branch=main#d91ecdf7d85443873c0b903b1dd0ca8ae24ec54a" dependencies = [ "console-api", "crossbeam-channel", + "crossbeam-utils", "futures", "hdrhistogram", "humantime", + "prost-types", "serde", "serde_json", "thread_local", @@ -387,17 +431,11 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -411,27 +449,27 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if", "crossbeam-utils", @@ -450,9 +488,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" dependencies = [ "cfg-if", "crossbeam-utils", @@ -463,9 +501,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -473,9 +511,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if", "lazy_static", @@ -501,15 +539,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "deflate" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70" -dependencies = [ - "adler32", -] - [[package]] name = "deflate" version = "1.0.0" @@ -519,46 +548,13 @@ dependencies = [ "adler32", ] -[[package]] -name = "derive-new" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer", "crypto-common", "subtle", ] @@ -571,9 +567,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "ed25519-compact" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302ea73924517e9952bf08b505536f757e28dca8372cbf8b20723a0e2bab6c01" +checksum = "24e1f30f0312ac83726c1197abeacd91c9557f8a623e904a009ae6bc529ae8d8" dependencies = [ "getrandom", ] @@ -585,74 +581,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "encoding" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -dependencies = [ - "encoding-index-japanese", - "encoding-index-korean", - "encoding-index-simpchinese", - "encoding-index-singlebyte", - "encoding-index-tradchinese", -] - -[[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-korean" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" +name = "email-encoding" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +checksum = "6690291166824e467790ac08ba42f241791567e8337bbf00c5a6e87889629f98" dependencies = [ - "encoding_index_tests", + "base64", ] -[[package]] -name = "encoding_index_tests" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" - [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if", ] @@ -676,12 +617,12 @@ dependencies = [ [[package]] name = "exr" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4badb9489a465cb2c555af1f00f0bfd8cecd6fc12ac11da9d5b40c5dd5f0200" +checksum = "14cc0e06fb5f67e5d6beadf3a382fec9baca1aa751c6d5368fdeee7e5932c215" dependencies = [ "bit_field", - "deflate 1.0.0", + "deflate", "flume", "half", "inflate", @@ -707,9 +648,9 @@ checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" dependencies = [ "cfg-if", "crc32fast", @@ -719,15 +660,15 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d04dafd11240188e146b6f6476a898004cace3be31d4ec5e08e216bf4947ac0" +checksum = "843c03199d0c0ca54bc1ea90ac0d507274c28abcc4f691ae8b4eaa375087c76a" dependencies = [ "futures-core", "futures-sink", "nanorand", "pin-project", - "spin 0.9.2", + "spin 0.9.3", ] [[package]] @@ -764,9 +705,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "futf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", @@ -774,9 +715,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -788,9 +729,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -798,15 +739,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -826,9 +767,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" @@ -847,9 +788,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -867,21 +808,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-core", "futures-io", @@ -915,14 +856,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -960,9 +901,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -973,7 +914,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.1", "tracing", ] @@ -998,7 +939,7 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "harmony_build" version = "0.1.0" -source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=master#3a7f65af8aefcfb3e808fce7d60b0defd6772f45" +source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=refactored#96b75d8f7b065b1269e186d8b43eb306843478e1" dependencies = [ "hrpc-build", "prost-build", @@ -1009,7 +950,7 @@ dependencies = [ [[package]] name = "harmony_derive" version = "0.1.3" -source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=master#3a7f65af8aefcfb3e808fce7d60b0defd6772f45" +source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=refactored#96b75d8f7b065b1269e186d8b43eb306843478e1" dependencies = [ "proc-macro2", "quote", @@ -1019,11 +960,9 @@ dependencies = [ [[package]] name = "harmony_rust_sdk" version = "0.8.0" -source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=master#3a7f65af8aefcfb3e808fce7d60b0defd6772f45" +source = "git+https://github.com/harmony-development/harmony_rust_sdk.git?branch=refactored#96b75d8f7b065b1269e186d8b43eb306843478e1" dependencies = [ "bytecheck", - "derive-new", - "derive_more", "harmony_build", "harmony_derive", "hrpc", @@ -1049,20 +988,29 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hashlink" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" dependencies = [ - "hashbrown", + "hashbrown 0.11.2", ] [[package]] name = "hdrhistogram" -version = "7.4.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6490be71f07a5f62b564bc58e36953f675833df11c7e4a0647bee7a07ca1ec5e" +checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" dependencies = [ "base64", "byteorder", @@ -1073,9 +1021,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" dependencies = [ "unicode-segmentation", ] @@ -1108,16 +1056,15 @@ dependencies = [ [[package]] name = "hrpc" -version = "0.33.22" +version = "0.33.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7808b92a1a40d44ed5ea66d2fca9dbabe5d9a694d95c5ca49b05b87886345135" +checksum = "e202d7fc9f596c3dc80f3af80fdcbbc54164f42b37b2cdf119b7d22562739a0e" dependencies = [ "axum-server", "base64", "bytes", "futures-channel", "futures-util", - "hrpc-build", "hrpc-proc-macro", "http", "http-body", @@ -1127,7 +1074,7 @@ dependencies = [ "pin-project-lite", "prost", "prost-build", - "sha-1 0.10.0", + "sha-1", "tokio", "tokio-tungstenite", "tower", @@ -1136,9 +1083,9 @@ dependencies = [ [[package]] name = "hrpc-build" -version = "0.33.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370eb264a57b2841bf588ab01a4de725df1a99a0410956b3010dc3663044f34d" +checksum = "ace803952c7504e41d30016a4e2573cc3f97bdd32479f951eb3e68c2b48d6a24" dependencies = [ "proc-macro2", "prost-build", @@ -1154,9 +1101,9 @@ checksum = "c4b491508ac546892d0218449993ca8def58c98e3cfffa7073fb0f67843ee461" [[package]] name = "html5ever" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" dependencies = [ "log", "mac", @@ -1168,13 +1115,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] @@ -1196,9 +1143,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" -version = "1.5.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -1214,9 +1161,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ "bytes", "futures-channel", @@ -1227,7 +1174,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -1244,10 +1191,10 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "rustls 0.20.2", + "rustls 0.20.4", "rustls-native-certs", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", ] [[package]] @@ -1275,15 +1222,16 @@ dependencies = [ [[package]] name = "image" -version = "0.24.0-alpha" -source = "git+https://github.com/image-rs/image.git?branch=master#9d70ecf5b41cde89b7c724f9a76f7827acfad57e" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28edd9d7bc256be2502e325ac0628bde30b7001b9b52e0abe31a1a9dc2701212" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", - "jpeg-decoder 0.2.1", + "jpeg-decoder", "num-iter", "num-rational", "num-traits", @@ -1294,12 +1242,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -1331,15 +1279,15 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "1.1.7" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dc51180a9b377fd75814d0cc02199c20f8e99433d6762f650d39cdbbd3b56f" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "ipnet" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" @@ -1350,18 +1298,31 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jemalloc-sys" +version = "0.4.3+5.2.1-patched.2" +source = "git+https://github.com/tikv/jemallocator.git?branch=master#161451ba9b7a2e209ade8bba757db95e70f4c45b" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.4.3" +source = "git+https://github.com/tikv/jemallocator.git?branch=master#161451ba9b7a2e209ade8bba757db95e70f4c45b" +dependencies = [ + "jemalloc-sys", + "libc", +] + [[package]] name = "jobserver" version = "0.1.24" @@ -1373,34 +1334,22 @@ dependencies = [ [[package]] name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" - -[[package]] -name = "jpeg-decoder" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcf0244f6597be39ab8d9203f574cafb529ae8c698afa2182f7b3c3205a4a9c" +checksum = "744c24117572563a98a7e9168a5ac1ee4a1ca7f702211258797bbe0ed0346c3c" dependencies = [ "rayon", ] [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keccak" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1415,12 +1364,13 @@ checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff" [[package]] name = "lettre" -version = "0.10.0-rc.4" +version = "0.10.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d8da8f34d086b081c9cc3b57d3bb3b51d16fc06b5c848a188e2f14d58ac2a5" +checksum = "2f6c70001f7ee6c93b6687a06607c7a38f9a7ae460139a496c23da21e95bc289" dependencies = [ "async-trait", "base64", + "email-encoding", "fastrand", "futures-io", "futures-util", @@ -1432,24 +1382,24 @@ dependencies = [ "once_cell", "quoted_printable", "regex", - "rustls 0.20.2", - "rustls-pemfile", + "rustls 0.20.4", + "rustls-pemfile 1.0.0", "tokio", - "tokio-rustls 0.23.2", - "webpki-roots 0.22.2", + "tokio-rustls 0.23.3", + "webpki-roots 0.22.3", ] [[package]] name = "libc" -version = "0.2.116" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libsqlite3-sys" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" dependencies = [ "cc", "pkg-config", @@ -1458,37 +1408,38 @@ dependencies = [ [[package]] name = "libwebp-sys" -version = "0.2.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e70c064738b35a28fd6f991d27c0d9680353641d167ae3702a8228dd8272ef6" +checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf" dependencies = [ "cc", ] [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", "serde", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", ] [[package]] name = "lru" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274353858935c992b13c0ca408752e2121da852d07dec7ce5f108c77dfa14d1f" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" [[package]] name = "mac" @@ -1527,6 +1478,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.9" @@ -1535,15 +1495,15 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matchit" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b6f41fdfbec185dd3dff58b51e323f5bc61692c0de38419a957b0dcfccca3c" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" [[package]] name = "mediasoup" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517582a7a65fefeb305b9a9b9dc711a5f91a28bc0d2f4a52d8bca935a4a7b4aa" +checksum = "447fce54609d17f3a91084cd6911cfb4e0e117a637617054e17a78859c830572" dependencies = [ "async-channel", "async-executor", @@ -1572,15 +1532,15 @@ dependencies = [ [[package]] name = "mediasoup-sys" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83eda72bddffb50bd363b3d598a9c4634f7458be4b5ec67c459b38237b6d301" +checksum = "4475c581332565ff35669f3dbb155a4dbbe227932759c281d78d3d1adbb7f98d" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -1605,24 +1565,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.7.14" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -1649,9 +1609,9 @@ dependencies = [ "log", "memchr", "mime", - "spin 0.9.2", + "spin 0.9.3", "tokio", - "tokio-util", + "tokio-util 0.6.9", "version_check", ] @@ -1663,9 +1623,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "nanorand" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ "getrandom", ] @@ -1684,29 +1644,28 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1714,9 +1673,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -1755,15 +1714,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "opaque-debug" -version = "0.3.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "openssl-probe" @@ -1773,8 +1726,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.16.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust.git?rev=482772f2317e242e6b98bfe04ed42e4dac8cf77b#482772f2317e242e6b98bfe04ed42e4dac8cf77b" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" dependencies = [ "async-trait", "crossbeam-channel", @@ -1793,8 +1747,9 @@ dependencies = [ [[package]] name = "opentelemetry-jaeger" -version = "0.15.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust.git?rev=482772f2317e242e6b98bfe04ed42e4dac8cf77b#482772f2317e242e6b98bfe04ed42e4dac8cf77b" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c0b12cd9e3f9b35b52f6e0dac66866c519b26f424f4bbf96e3fe8bfbdc5229" dependencies = [ "async-trait", "lazy_static", @@ -1807,8 +1762,9 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.8.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust.git?rev=482772f2317e242e6b98bfe04ed42e4dac8cf77b#482772f2317e242e6b98bfe04ed42e4dac8cf77b" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985cc35d832d412224b2cffe2f9194b1b89b6aa5d0bef76d080dce09d90e62bd" dependencies = [ "opentelemetry", ] @@ -1846,7 +1802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.0", + "parking_lot_core 0.9.3", ] [[package]] @@ -1865,9 +1821,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f4f894f3865f6c0e02810fc597300f34dc2510f66400da262d8ae10e75767d" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", @@ -1878,9 +1834,9 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +checksum = "e029e94abc8fb0065241c308f1ac6bc8d20f450e8f7c5f0b25cd9b8d526ba294" dependencies = [ "base64ct", "rand_core", @@ -1899,9 +1855,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "paste-impl" @@ -1988,9 +1944,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -2000,20 +1956,19 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "png" -version = "0.17.2" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c845088517daa61e8a57eee40309347cea13f273694d1385c553e7a57127763b" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" dependencies = [ "bitflags", "crc32fast", - "deflate 0.9.1", - "encoding", + "deflate", "miniz_oxide", ] @@ -2037,18 +1992,18 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "a07b0857a71a8cb765763950499cae2413c3f9cede1133478c43600d9e146890" dependencies = [ "bytes", "prost-derive", @@ -2056,11 +2011,13 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "120fbe7988713f39d780a58cf1a7ef0d7ef66c6d87e5aa3438940c05357929f4" dependencies = [ "bytes", + "cfg-if", + "cmake", "heck", "itertools", "lazy_static", @@ -2076,9 +2033,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" dependencies = [ "anyhow", "itertools", @@ -2089,9 +2046,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", "prost", @@ -2119,9 +2076,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -2134,14 +2091,13 @@ checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -2163,20 +2119,11 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" dependencies = [ "autocfg", "crossbeam-deque", @@ -2186,37 +2133,45 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.25" @@ -2243,9 +2198,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "async-compression", "base64", @@ -2265,15 +2220,15 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.20.2", + "rustls 0.20.4", "rustls-native-certs", - "rustls-pemfile", + "rustls-pemfile 0.3.0", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.23.2", - "tokio-util", + "tokio-rustls 0.23.3", + "tokio-util 0.6.9", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -2298,12 +2253,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.31" +version = "0.7.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439655b8d657bcb28264da8e5380d55549e34ffc4149bea9e3521890a122a7bd" +checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" dependencies = [ "bytecheck", - "hashbrown", + "hashbrown 0.12.0", "ptr_meta", "rend", "rkyv_derive", @@ -2312,24 +2267,15 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.31" +version = "0.7.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cded413ad606a80291ca84bedba137093807cf4f5b36be8c60f57a7e790d48f6" +checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustls" version = "0.19.1" @@ -2345,9 +2291,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ "log", "ring", @@ -2357,21 +2303,30 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.0", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" dependencies = [ "base64", ] @@ -2410,6 +2365,7 @@ dependencies = [ "argon2", "bytecheck", "console-subscriber", + "crossbeam-epoch", "dashmap", "ed25519-compact", "git-version", @@ -2421,6 +2377,7 @@ dependencies = [ "image", "infer", "itertools", + "jemallocator", "lazy_static", "lettre", "mediasoup", @@ -2428,7 +2385,7 @@ dependencies = [ "opentelemetry", "opentelemetry-jaeger", "parking_lot 0.12.0", - "paste 1.0.6", + "paste 1.0.7", "pin-project", "prost", "rand", @@ -2437,14 +2394,11 @@ dependencies = [ "scherzo_derive", "serde", "serde_json", - "sha3", "sled", "smol_str", "sqlx", - "swimmer", - "tikv-jemallocator", "tokio", - "tokio-util", + "tokio-util 0.7.1", "toml", "tower", "tower-http", @@ -2506,9 +2460,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -2519,20 +2473,14 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", ] -[[package]] -name = "semver" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" - [[package]] name = "serde" version = "1.0.136" @@ -2555,11 +2503,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] @@ -2582,24 +2530,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa", "ryu", "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha-1" version = "0.10.0" @@ -2608,17 +2543,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", -] - -[[package]] -name = "sha3" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" -dependencies = [ - "digest 0.10.3", - "keccak", + "digest", ] [[package]] @@ -2641,15 +2566,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "sled" @@ -2676,9 +2601,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smol_str" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd" +checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" dependencies = [ "serde", ] @@ -2701,9 +2626,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" dependencies = [ "lock_api", ] @@ -2721,9 +2646,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692749de69603d81e016212199d73a2e14ee20e2def7d7914919e8db5d4d48b9" +checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2731,19 +2656,18 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518be6f6fff5ca76f985d434f9c37f3662af279642acf730388f271dff7b9016" +checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" dependencies = [ "ahash", "atoi", "bitflags", "byteorder", "bytes", - "crossbeam-channel", "crossbeam-queue", - "crossbeam-utils", "either", + "event-listener", "flume", "futures-channel", "futures-core", @@ -2753,13 +2677,13 @@ dependencies = [ "hashlink", "hex", "indexmap", - "itoa 1.0.1", + "itoa", "libc", "libsqlite3-sys", "log", "memchr", "once_cell", - "parking_lot 0.11.2", + "paste 1.0.7", "percent-encoding", "rustls 0.19.1", "smallvec", @@ -2775,9 +2699,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e45140529cf1f90a5e1c2e561500ca345821a1c513652c8f486bbf07407cc8" +checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" dependencies = [ "dotenv", "either", @@ -2793,9 +2717,9 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8061cbaa91ee75041514f67a09398c65a64efed72c90151ecd47593bad53da99" +checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" dependencies = [ "once_cell", "tokio", @@ -2841,25 +2765,23 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "swimmer" -version = "0.3.0" -source = "git+https://github.com/yusdacra/swimmer-rs.git?branch=master#5f5735a6f8a6e3c61624ab7756943c8fa0cc2793" -dependencies = [ - "thread_local", -] - [[package]] name = "syn" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + [[package]] name = "tempfile" version = "3.3.0" @@ -2876,9 +2798,9 @@ dependencies = [ [[package]] name = "tendril" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", @@ -2925,9 +2847,9 @@ dependencies = [ [[package]] name = "thrift" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6d965454947cc7266d22716ebfd07b18d84ebaf35eec558586bbb2a8cb6b5b" +checksum = "b82ca8f46f95b3ce96081fe3dd89160fdea970c254bb72925255d1b62aae692e" dependencies = [ "byteorder", "integer-encoding", @@ -2938,34 +2860,15 @@ dependencies = [ [[package]] name = "tiff" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0247608e998cb6ce39dfc8f4a16c50361ce71e5b52e6d24ea1227ea8ea8ee0b2" +checksum = "7cfada0986f446a770eca461e8c6566cb879682f7d687c8348aa0c857bd52286" dependencies = [ "flate2", - "jpeg-decoder 0.1.22", + "jpeg-decoder", "weezl", ] -[[package]] -name = "tikv-jemalloc-sys" -version = "0.4.2+5.2.1-patched.2" -source = "git+https://github.com/tikv/jemallocator.git?branch=master#52de4257fab3e770f73d5174c12a095b49572fba" -dependencies = [ - "cc", - "fs_extra", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.4.1" -source = "git+https://github.com/tikv/jemallocator.git?branch=master#52de4257fab3e770f73d5174c12a095b49572fba" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - [[package]] name = "time" version = "0.1.43" @@ -2978,9 +2881,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2993,9 +2896,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.16.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" dependencies = [ "bytes", "libc", @@ -3005,6 +2908,7 @@ dependencies = [ "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "tracing", "winapi", @@ -3044,11 +2948,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" dependencies = [ - "rustls 0.20.2", + "rustls 0.20.4", "tokio", "webpki 0.22.0", ] @@ -3066,16 +2970,16 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80b39df6afcc12cdf752398ade96a6b9e99c903dfdc36e53ad10b9c366bca72" +checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" dependencies = [ "futures-util", "log", - "rustls 0.20.2", + "rustls 0.20.4", "rustls-native-certs", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", "tungstenite", "webpki 0.22.0", ] @@ -3094,23 +2998,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "tonic" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "30fb54bf1e446f44d870d260d99957e7d11fb9d0a0f5bd1a662ad1411cc103f9" dependencies = [ "async-stream", "async-trait", + "axum", "base64", "bytes", "futures-core", @@ -3126,7 +3045,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.1", "tower", "tower-layer", "tower-service", @@ -3134,23 +3053,11 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "tonic-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" -dependencies = [ - "proc-macro2", - "prost-build", - "quote", - "syn", -] - [[package]] name = "tower" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5651b5f6860a99bd1adb59dbfe1db8beb433e73709d9032b413a77e2fb7c066a" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", @@ -3160,8 +3067,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-stream", - "tokio-util", + "tokio-util 0.7.1", "tower-layer", "tower-service", "tracing", @@ -3169,9 +3075,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03650267ad175b51c47d02ed9547fc7d4ba2c7e5cb76df0bed67edd1825ae297" +checksum = "e980386f06883cf4d0578d6c9178c81f68b45d77d00f2c2c1bc034b3439c2c56" dependencies = [ "bitflags", "bytes", @@ -3181,6 +3087,7 @@ dependencies = [ "http-body", "http-range-header", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -3200,9 +3107,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if", "log", @@ -3213,9 +3120,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -3224,11 +3131,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -3243,9 +3151,9 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", @@ -3254,26 +3162,31 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.16.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffbf13a0f8b054a4e59df3a173b818e9c6177c02789871f2073977fd0062076" +checksum = "1f9378e96a9361190ae297e7f3a8ff644aacd2897f244b1ff81f381669196fa6" dependencies = [ "opentelemetry", "tracing", "tracing-core", + "tracing-log", "tracing-subscriber", ] [[package]] name = "tracing-subscriber" -version = "0.3.7" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ "ansi_term", + "lazy_static", + "matchers", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] @@ -3295,9 +3208,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.16.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ "base64", "byteorder", @@ -3306,8 +3219,8 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.20.2", - "sha-1 0.9.8", + "rustls 0.20.4", + "sha-1", "thiserror", "url", "utf-8", @@ -3322,9 +3235,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-normalization" @@ -3337,9 +3250,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-xid" @@ -3393,6 +3306,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3438,11 +3357,17 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3450,9 +3375,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -3465,9 +3390,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if", "js-sys", @@ -3477,9 +3402,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3487,9 +3412,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -3500,15 +3425,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -3516,8 +3441,9 @@ dependencies = [ [[package]] name = "webp" -version = "0.2.0" -source = "git+https://github.com/yusdacra/webp.git?branch=master#8f721ef1c7fd90da57a66132667b1d99484653f6" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae" dependencies = [ "image", "libwebp-sys", @@ -3564,24 +3490,24 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" dependencies = [ "webpki 0.22.0", ] [[package]] name = "weezl" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" +checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -3621,9 +3547,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceb069ac8b2117d36924190469735767f0990833935ab430155e71a44bafe148" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -3634,39 +3560,39 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.29.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index ecde11d..8c145e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,15 +20,15 @@ required-features = ["sled", "sqlite"] [features] default = ["sled"] -voice = ["mediasoup"] -jemalloc = ["tikv-jemallocator"] +webrtc = ["mediasoup"] +jemalloc = ["jemallocator"] # dbs sqlite = ["sqlx", "itertools"] [dependencies] scherzo_derive = { path = "./scherzo_derive" } -harmony_rust_sdk = { git = "https://github.com/harmony-development/harmony_rust_sdk.git", branch = "master", features = [ +harmony_rust_sdk = { git = "https://github.com/harmony-development/harmony_rust_sdk.git", branch = "refactored", features = [ "gen_all_protocols", "gen_client", "gen_server", @@ -36,7 +36,7 @@ harmony_rust_sdk = { git = "https://github.com/harmony-development/harmony_rust_ "rkyv", "rkyv_validation", ] } -prost = { version = "0.9" } +prost = { version = "0.10" } hrpc = { version = "0.33", features = [ "http_server", "http_hyper_client", @@ -57,11 +57,12 @@ hyper-rustls = { version = "0.23", default-features = false, features = [ "http2", ] } tower = { version = "0.4", default-features = false, features = ["limit"] } -tower-http = { version = "0.2", default-features = false, features = [ +tower-http = { version = "0.3", default-features = false, features = [ "trace", "sensitive-headers", "map-response-body", "cors", + "catch-panic", ] } multer = { version = "2.0", default-features = false, features = ["tokio-io"] } sled = { version = "0.34.6", features = ["compression"], optional = true } @@ -82,10 +83,9 @@ itertools = { version = "0.10", default-features = false, features = [ ], optional = true } rand = "0.8" -argon2 = "0.3" +argon2 = "0.4" ed25519-compact = "1" -sha3 = "0.10" -ahash = { version = "0.7", default-features = false } +ahash = "0.7" tokio = { version = "1.16", features = [ "macros", @@ -94,8 +94,7 @@ tokio = { version = "1.16", features = [ "tracing", "signal", ] } -tokio-util = "0.6.7" -swimmer = "0.3" +tokio-util = { version = "0.7", features = ["io"] } tracing = "0.1" tracing-subscriber = { version = "0.3", default-features = false, features = [ @@ -106,12 +105,13 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ "std", ] } console-subscriber = { git = "https://github.com/tokio-rs/console.git", branch = "main" } -opentelemetry = { version = "0.16", features = ["rt-tokio"] } -opentelemetry-jaeger = { version = "0.15", features = ["rt-tokio"] } -tracing-opentelemetry = { version = "0.16", default-features = false } +opentelemetry = { version = "0.17", features = ["rt-tokio"] } +opentelemetry-jaeger = { version = "0.16", features = ["rt-tokio"] } +tracing-opentelemetry = { version = "0.17" } pin-project = "1" +crossbeam-epoch = "=0.9.7" dashmap = "=4.0" webpage = { git = "https://github.com/yusdacra/webpage-rs.git", branch = "chore/deps", default-features = false } paste = "1.0" @@ -120,8 +120,8 @@ lazy_static = "1.4" smol_str = { version = "0.1", features = ["serde"] } git-version = "0.3" triomphe = { version = "0.1", default-features = false } -image = { git = "https://github.com/image-rs/image.git", branch = "master" } -webp = { git = "https://github.com/yusdacra/webp.git", branch = "master" } +image = "0.24" +webp = "0.2" infer = "0.7" anyhow = "1" @@ -135,10 +135,10 @@ bytecheck = { version = "0.6" } mediasoup = { version = "0.9", optional = true } -tikv-jemallocator = { git = "https://github.com/tikv/jemallocator.git", branch = "master", optional = true } +jemallocator = { git = "https://github.com/tikv/jemallocator.git", branch = "master", optional = true } [build-dependencies] -harmony_build = { git = "https://github.com/harmony-development/harmony_rust_sdk.git", branch = "master", features = [ +harmony_build = { git = "https://github.com/harmony-development/harmony_rust_sdk.git", branch = "refactored", features = [ "client", "server", "all_permissions", @@ -178,7 +178,4 @@ key = "harmony.cachix.org-1:yv78QZHgS0UHkrMW56rccNghWHRz18fFRl8mWQ63M6E=" [patch.crates-io] markup5ever = { git = "https://github.com/servo/html5ever.git", rev = "0e03e1c2b1f63e81f831fd95b9eb8bbde18b7815" } string_cache = { git = "https://github.com/yusdacra/string-cache.git", branch = "chore/deps" } -string_cache_codegen = { git = "https://github.com/yusdacra/string-cache.git", branch = "chore/deps" } -opentelemetry-jaeger = { git = "https://github.com/open-telemetry/opentelemetry-rust.git", rev = "482772f2317e242e6b98bfe04ed42e4dac8cf77b" } -opentelemetry = { git = "https://github.com/open-telemetry/opentelemetry-rust.git", rev = "482772f2317e242e6b98bfe04ed42e4dac8cf77b" } -swimmer = { git = "https://github.com/yusdacra/swimmer-rs.git", branch = "master" } \ No newline at end of file +string_cache_codegen = { git = "https://github.com/yusdacra/string-cache.git", branch = "chore/deps" } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 73f25b4..f08f196 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,87 @@ -FROM alpine:3.12 as builder +# syntax=docker/dockerfile:1 +FROM alpine:edge AS builder +WORKDIR /usr/src/scherzo -RUN apk add --no-cache curl +# Install required packages to build scherzo and it's dependencies +RUN apk add --no-cache cargo rust protobuf cmake -RUN cd /root && curl -L https://github.com/harmony-development/scherzo/releases/download/continuous/scherzo > scherzo && chmod +x scherzo +ENV RUSTC_BOOTSTRAP=1 \ + RUSTFLAGS="--cfg tokio_unstable" -FROM alpine:3.12 +# == Build dependencies without our own code separately for caching == +# +# Need a fake main.rs since Cargo refuses to build anything otherwise. +# +# See https://github.com/rust-lang/cargo/issues/2644 for a Cargo feature +# request that would allow just dependencies to be compiled, presumably +# regardless of whether source files are available. +RUN mkdir -p src/bin \ + && touch src/lib.rs \ + && echo 'fn main() {}' > src/main.rs \ + && echo 'fn main() {}' > src/bin/cmd.rs \ + && echo 'fn main() {}' > src/bin/migrate.rs +COPY scherzo_derive scherzo_derive +COPY Cargo.toml Cargo.lock ./ +RUN cargo build --release && rm -r src -EXPOSE 2289 +# Copy over actual scherzo sources +COPY src src +COPY resources resources +COPY protocols protocols +COPY example_config.toml build.rs ./ -RUN mkdir -p /srv/scherzo -COPY --from=builder /root/scherzo /srv/scherzo/ +# main.rs and lib.rs need their timestamp updated for this to work correctly since +# otherwise the build with the fake main.rs from above is newer than the +# source files (COPY preserves timestamps). +# +# Builds scherzo and places the binary at /usr/src/conduit/scherzo/release/scherzo +RUN touch src/main.rs \ + && touch src/lib.rs \ + && touch src/bin/cmd.rs \ + && touch src/bin/migrate.rs \ + && cargo build --release -RUN echo "listen_on_localhost = false" > /srv/scherzo/config.toml +# --------------------------------------------------------------------------------------------------------------- +# Stuff below this line actually ends up in the resulting docker image +# --------------------------------------------------------------------------------------------------------------- +FROM alpine:latest AS runner -RUN set -x ; \ - addgroup -Sg 82 www-data 2>/dev/null ; \ - adduser -S -D -H -h /srv/scherzo -G www-data -g www-data www-data 2>/dev/null ; \ - addgroup www-data www-data 2>/dev/null && exit 0 ; exit 1 +# Standard port on which scherzo launches. +# You still need to map the port when using the docker command or docker-compose. +EXPOSE 2289 -RUN chown -cR www-data:www-data /srv/scherzo +# scherzo needs: +# ca-certificates: for https +# curl: for the healthcheck script +# libgcc: for scherzo bin +# shadow: needed for useradd groupadd +RUN apk add --no-cache ca-certificates curl shadow libgcc -RUN apk add --no-cache \ - curl \ - ca-certificates \ - libgcc +# Created directory for the database and media files +RUN mkdir -p /srv/scherzo -VOLUME ["/srv/scherzo/db", "/srv/scherzo/media", "/srv/scherzo/logs"] +# Test if scherzo is still alive +HEALTHCHECK --start-period=5s --interval=5s CMD curl --fail -s http://localhost:2289/_harmony/about || curl -k --fail -s https://localhost:2289/_harmony/about || exit 1 -HEALTHCHECK --start-period=2s CMD curl --fail -s http://localhost:2289/_harmony/about || curl -k --fail -s https://localhost:2289/_harmony/about || exit 1 +# Copy over the actual scherzo binary from the builder stage +COPY --from=builder /usr/src/scherzo/target/release/scherzo /srv/scherzo/scherzo -USER www-data +# Improve security: Don't run stuff as root, that does not need to run as root +# Most distros also use 1000:1000 for the first real user, so this should resolve volume mounting problems. +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN set -x ; \ + groupadd -r -g ${GROUP_ID} scherzo ; \ + useradd -l -r -M -d /srv/scherzo -o -u ${USER_ID} -g scherzo scherzo && exit 0 ; exit 1 + +# Change ownership of scherzo files to scherzo user and group and make the healthcheck executable: +RUN chown -cR scherzo:scherzo /srv/scherzo + +# Change user to scherzo, no root permissions afterwards: +USER scherzo +# Set container home directory WORKDIR /srv/scherzo -ENTRYPOINT [ "/srv/scherzo/scherzo" ] \ No newline at end of file + +# Run scherzo and print backtraces on panics +ENV RUST_BACKTRACE=1 +ENTRYPOINT [ "/srv/scherzo/scherzo" ] diff --git a/build.rs b/build.rs index e805109..cdca5f9 100644 --- a/build.rs +++ b/build.rs @@ -5,24 +5,16 @@ fn main() -> Result<()> { "before_account_kind", &["profile.v1", "harmonytypes.v1"], &[], - |builder| { - builder.modify_hrpc_config(|cfg| { - cfg.type_attribute( - ".protocol.profile.v1.Profile", - "#[archive_attr(derive(::bytecheck::CheckBytes))]", - ) - }) - }, + )?; + build_protocol( + "before_proto_v2", + &["profile.v1", "emote.v1", "chat.v1", "harmonytypes.v1"], + &[], )?; Ok(()) } -fn build_protocol( - version: &str, - stable_svcs: &[&str], - staging_svcs: &[&str], - f: impl FnOnce(Builder) -> Builder, -) -> Result<()> { +fn build_protocol(version: &str, stable_svcs: &[&str], staging_svcs: &[&str]) -> Result<()> { let protocol_path = format!("protocols/{}", version); let out_dir = { let mut dir = std::env::var("OUT_DIR").expect("no out dir, how"); @@ -42,17 +34,19 @@ fn build_protocol( cfg }); - for service in all_services.filter(|a| "batch.v1".ne(**a)) { + for service in all_services { builder = builder.modify_hrpc_config(|cfg| { cfg.type_attribute( format!(".protocol.{}", service), "#[derive(::rkyv::Archive, ::rkyv::Serialize, ::rkyv::Deserialize)]", ) + .type_attribute( + format!(".protocol.{}", service), + "#[archive_attr(derive(::bytecheck::CheckBytes))]", + ) }); } - let builder = f(builder); - builder.generate(protocol, out_dir)?; Ok(()) diff --git a/default.nix b/default.nix index 78f09ba..054d96c 100644 --- a/default.nix +++ b/default.nix @@ -1,12 +1,14 @@ # Flake's default package for non-flake-enabled nix instances (import ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = - "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flakeCompat.locked.narHash; - } + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flakeCompat.locked.narHash; + } ) - { src = ./.; }).defaultNix.default + {src = ./.;}) +.defaultNix +.default diff --git a/deny.toml b/deny.toml index 820b503..ad4e644 100644 --- a/deny.toml +++ b/deny.toml @@ -83,7 +83,6 @@ allow = [ "ISC", "OpenSSL", "Zlib", - "CC0-1.0", "BSL-1.0", "0BSD", ] @@ -210,7 +209,6 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [ "https://github.com/servo/html5ever.git", - "https://github.com/open-telemetry/opentelemetry-rust.git", "https://github.com/tokio-rs/console.git", "https://github.com/image-rs/image.git", ] diff --git a/flake.lock b/flake.lock index 1fee69e..2485e3b 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,35 @@ { "nodes": { + "crane": { + "flake": false, + "locked": { + "lastModified": 1644785799, + "narHash": "sha256-VpAJO1L0XeBvtCuNGK4IDKp6ENHIpTrlaZT7yfBCvwo=", + "owner": "ipetkov", + "repo": "crane", + "rev": "fc7a94f841347c88f2cb44217b2a3faa93e2a0b2", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nci", + "nixpkgs" + ] + }, "locked": { - "lastModified": 1642188268, - "narHash": "sha256-DNz4xScpXIn7rSDohdayBpPR9H9OWCMDOgTYegX081k=", + "lastModified": 1650900878, + "narHash": "sha256-qhNncMBSa9STnhiLfELEQpYC1L4GrYHNIzyCZ/pilsI=", "owner": "numtide", "repo": "devshell", - "rev": "696acc29668b644df1740b69e1601119bf6da83b", + "rev": "d97df53b5ddaa1cfbea7cddbd207eb2634304733", "type": "github" }, "original": { @@ -15,14 +38,79 @@ "type": "github" } }, + "dream2nix": { + "inputs": { + "alejandra": [ + "nci", + "nixpkgs" + ], + "crane": "crane", + "flake-utils-pre-commit": [ + "nci", + "nixpkgs" + ], + "gomod2nix": [ + "nci", + "nixpkgs" + ], + "mach-nix": [ + "nci", + "nixpkgs" + ], + "nixpkgs": [ + "nci", + "nixpkgs" + ], + "node2nix": [ + "nci", + "nixpkgs" + ], + "poetry2nix": [ + "nci", + "nixpkgs" + ], + "pre-commit-hooks": [ + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1651223854, + "narHash": "sha256-VFeEmzUiTSvilzKKNVPbfgXTn7EvrQol//9PDbLba+8=", + "owner": "nix-community", + "repo": "dream2nix", + "rev": "97d32e314e4621306adbbc2cdc71c0e84dbbd9ed", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "dream2nix", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1642700792, + "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "flakeCompat": { "flake": false, "locked": { - "lastModified": 1641205782, - "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=", + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "owner": "edolstra", "repo": "flake-compat", - "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", "type": "github" }, "original": { @@ -31,36 +119,36 @@ "type": "github" } }, - "nixCargoIntegration": { + "nci": { "inputs": { "devshell": "devshell", + "dream2nix": "dream2nix", "nixpkgs": [ "nixpkgs" ], "rustOverlay": "rustOverlay" }, "locked": { - "lastModified": 1642745416, - "narHash": "sha256-i87+cNS0raIgHEhNdvBS9OhWm8Dam+PiWe7lKxPMZ9g=", + "lastModified": 1651299115, + "narHash": "sha256-CYIBsYOvQxbxLvuS6tTGa9/fbYMt++bqqxKI6D1mTfE=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "20c4403b45b86f33a35b55569544e35cd7772927", + "rev": "84ca864e64a7c31a879279f18a64a610d19a10e6", "type": "github" }, "original": { "owner": "yusdacra", - "ref": "master", "repo": "nix-cargo-integration", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1642635915, - "narHash": "sha256-vabPA32j81xBO5m3+qXndWp5aqepe+vu96Wkd9UnngM=", + "lastModified": 1651007983, + "narHash": "sha256-GNay7yDPtLcRcKCNHldug85AhAvBpTtPEJWSSDYBw8U=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6d8215281b2f87a5af9ed7425a26ac575da0438f", + "rev": "e10da1c7f542515b609f8dfbcf788f3d85b14936", "type": "github" }, "original": { @@ -73,18 +161,18 @@ "root": { "inputs": { "flakeCompat": "flakeCompat", - "nixCargoIntegration": "nixCargoIntegration", + "nci": "nci", "nixpkgs": "nixpkgs" } }, "rustOverlay": { "flake": false, "locked": { - "lastModified": 1642646417, - "narHash": "sha256-1PN44kOjxk6fYeeE8qTo8k+oa4Fa4Mg5UVLPVzuhBpA=", + "lastModified": 1651286718, + "narHash": "sha256-sPGOKDL6TNRfLnwarbdlmeD0FW4BmPfOoB/AMax91pg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "85dcf1a4e4897db4420f2c0a3eaf7bb4693914bc", + "rev": "8a687a6e5dc1f5c39715b01521a7aa0122529a05", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6e4ea13..f271dda 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,8 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - nixCargoIntegration = { - url = "github:yusdacra/nix-cargo-integration/master"; + nci = { + url = "github:yusdacra/nix-cargo-integration"; inputs.nixpkgs.follows = "nixpkgs"; }; flakeCompat = { @@ -11,58 +11,45 @@ }; }; - outputs = inputs: inputs.nixCargoIntegration.lib.makeOutputs { - root = ./.; - buildPlatform = "crate2nix"; - overrides = { - crateOverrides = common: _: { - mediasoup-sys = prev: - let + outputs = inputs: + inputs.nci.lib.makeOutputs { + root = ./.; + overrides = { + crates = common: _: { + mediasoup-sys = prev: let pkgs = common.pkgs; - pythonPkgs = pkgs: with pkgs; [ - pip - ]; + pythonPkgs = pkgs: with pkgs; [pip]; pythonWithPkgs = pkgs.python3.withPackages pythonPkgs; - all = (with pkgs; [ cmake gnumake nodejs meson ninja ]) ++ [ pythonWithPkgs ]; - in - { - buildInputs = (prev.buildInputs or [ ]) ++ all; - nativeBuildInputs = (prev.nativeBuildInputs or [ ]) ++ all; + all = (with pkgs; [cmake gnumake nodejs meson ninja]) ++ [pythonWithPkgs]; + in { + buildInputs = (prev.buildInputs or []) ++ all; + nativeBuildInputs = (prev.nativeBuildInputs or []) ++ all; }; - scherzo = prev: { - crateBin = common.lib.filter (bin: bin.name != "scherzo_migrate" && bin.name != "scherzo_cmd") prev.crateBin; }; - }; - shell = common: prev: { - packages = prev.packages ++ (with common.pkgs; [ - musl.dev - mold - mkcert - cargo-deny - /*(common.lib.buildCrate { - memberName = "tokio-console"; - - root = builtins.fetchGit { - url = "https://github.com/tokio-rs/console.git"; - rev = "a30264e0b5469ea596430b846b05e6e3541915d1"; - ref = "main"; - }; - - inherit (common) nativeBuildInputs buildInputs; - CARGO_PKG_REPOSITORY = "https://github.com/tokio-rs/console"; - })*/ - ]); - commands = prev.commands ++ [ - { - name = "generate-cert"; - command = '' - mkcert localhost 127.0.0.1 ::1 - mv localhost+2.pem cert.pem - mv localhost+2-key.pem key.pem - ''; - } - ]; + shell = common: prev: { + packages = + prev.packages + ++ (with common.pkgs; [ + mold + mkcert + ]); + commands = + prev.commands + ++ [ + { + name = "debug-server"; + command = ''cargo run --no-default-features --features sled -- -d''; + } + { + name = "generate-cert"; + command = '' + mkcert localhost 127.0.0.1 ::1 + mv localhost+2.pem cert.pem + mv localhost+2-key.pem key.pem + ''; + } + ]; + }; }; }; - }; } diff --git a/protocols/before_proto_v2 b/protocols/before_proto_v2 new file mode 160000 index 0000000..2aef2cc --- /dev/null +++ b/protocols/before_proto_v2 @@ -0,0 +1 @@ +Subproject commit 2aef2cca047c6f3db76af543b06133ee4bd15028 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6d7b8b5..b76046e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] -channel = "nightly-2022-01-20" -targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] \ No newline at end of file +channel = "nightly-2022-04-02" +targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] +components = ["rust-src", "rustfmt"] \ No newline at end of file diff --git a/scherzo_derive/src/lib.rs b/scherzo_derive/src/lib.rs index 5191132..4d3284f 100644 --- a/scherzo_derive/src/lib.rs +++ b/scherzo_derive/src/lib.rs @@ -6,27 +6,30 @@ use syn::{parse_macro_input, AttributeArgs, ItemFn}; #[proc_macro] pub fn define_proto_mod(input: TokenStream) -> TokenStream { let input = input.to_string(); - let mut split = input.split(',').collect::>(); - let (proto_name, svc) = if split.len() == 1 { - ("main", split.pop().unwrap()) + let mut split = input.split(',').map(str::trim).collect::>(); + let (proto_name, svcs) = if split.len() == 1 { + ("main", split) } else { - let svc = split.pop().unwrap().trim(); - let proto_name = split.pop().unwrap().trim(); - (proto_name, svc) + let proto_name = split.remove(0); + (proto_name, split) }; - let path = format!("/{}/protocol.{}.v1.rs", proto_name, svc); - let svc = Ident::new(svc, Span::call_site()); + let mut gen = TokenStream::new(); + for svc in svcs { + let path = format!("/{}/protocol.{}.v1.rs", proto_name, svc); + let svc = Ident::new(svc, Span::call_site()); - (quote! { - pub mod #svc { - pub mod v1 { - include!(concat!(env!("OUT_DIR"), #path)); + gen.extend(TokenStream::from(quote! { + pub mod #svc { + pub mod v1 { + include!(concat!(env!("OUT_DIR"), #path)); + } + pub use v1::*; } - pub use v1::*; - } - }) - .into() + })); + } + + gen } #[proc_macro] diff --git a/shell.nix b/shell.nix index bd18cba..8c9a5e6 100644 --- a/shell.nix +++ b/shell.nix @@ -1,12 +1,14 @@ # Flake's devShell for non-flake-enabled nix instances (import ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = - "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flakeCompat.locked.narHash; - } + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flakeCompat.locked.narHash; + } ) - { src = ./.; }).shellNix.default + {src = ./.;}) +.shellNix +.default diff --git a/src/db/migration/add_account_kind.rs b/src/db/migration/add_account_kind.rs index a587c8a..8ab6212 100644 --- a/src/db/migration/add_account_kind.rs +++ b/src/db/migration/add_account_kind.rs @@ -1,65 +1,31 @@ -use std::fmt::Display; - use super::*; -use db::{ - profile::{make_user_profile_key, USER_PREFIX}, - rkyv_ser, Batch, DbError, -}; -use harmony_rust_sdk::api::profile::AccountKind; -use hrpc::box_error; +use db::profile::USER_PREFIX; +use harmony_rust_sdk::api::profile::{user_status, AccountKind, UserStatus}; use crate::api::profile::Profile as NewProfile; -use profile::Profile as OldProfile; - -pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { - let fut = async move { - let profile_tree = db.open_tree(b"profile").await?; - let mut batch = Batch::default(); - for res in profile_tree.scan_prefix(USER_PREFIX).await { - let (key, val) = res?; - if key.len() == make_user_profile_key(0).len() { - let old_profile = rkyv::from_bytes::(&val); - if let Ok(old_profile) = old_profile { - let new_val = rkyv_ser(&NewProfile { - user_avatar: old_profile.user_avatar, - account_kind: AccountKind::FullUnspecified.into(), - is_bot: old_profile.is_bot, - user_name: old_profile.user_name, - user_status: old_profile.user_status, - }); - batch.insert(key, new_val); - } else if rkyv::check_archived_root::(&val).is_ok() { - // if it's new, then its already fine - continue; - } else { - old_profile.map_err(|err| DbError { - inner: box_error(AnyhowError(anyhow::anyhow!( - "profile with key {} has invalid state: {}", - String::from_utf8_lossy(key.as_ref()), - err - ))), - })?; - } - } +use profile::{Profile as OldProfile, UserStatus as OldUserStatus}; + +define_migration!(|db| { + let profile_tree = db.open_tree(b"profile").await?; + migrate_type::(&profile_tree, USER_PREFIX, |old_profile| { + NewProfile { + user_avatar: old_profile.user_avatar, + account_kind: AccountKind::FullUnspecified.into(), + user_name: old_profile.user_name, + user_status: Some(UserStatus { + kind: (match OldUserStatus::from_i32(old_profile.user_status).unwrap_or_default() { + OldUserStatus::Online => user_status::Kind::Online, + OldUserStatus::Idle => user_status::Kind::Idle, + OldUserStatus::DoNotDisturb => user_status::Kind::DoNotDisturb, + _ => user_status::Kind::OfflineUnspecified, + }) + .into(), + ..Default::default() + }), } - profile_tree.apply_batch(batch).await?; - Ok(()) - }; - - Box::pin(fut) -} - -scherzo_derive::define_proto_mod!(before_account_kind, profile); -scherzo_derive::define_proto_mod!(before_account_kind, harmonytypes); - -#[derive(Debug)] -struct AnyhowError(anyhow::Error); - -impl Display for AnyhowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} + }) + .await +}); -impl std::error::Error for AnyhowError {} +scherzo_derive::define_proto_mod!(before_account_kind, harmonytypes, profile); diff --git a/src/db/migration/add_next_msg_ids.rs b/src/db/migration/add_next_msg_ids.rs index dcf65fb..9e27d12 100644 --- a/src/db/migration/add_next_msg_ids.rs +++ b/src/db/migration/add_next_msg_ids.rs @@ -2,47 +2,43 @@ use super::*; use db::{chat::make_chan_key, deser_chan, Batch}; -pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { - const CHAN_KEY_LEN: usize = make_chan_key(0, 0).len(); - - let fut = async move { - let chat_tree = db.open_tree(b"chat").await?; - - let mut batch = Batch::default(); - for res in chat_tree.iter().await { - let (key, val) = res?; - let mut key: Vec = key.into(); - - if key.len() == CHAN_KEY_LEN && key[8] == 8 { - deser_chan(val); - key.push(9); - - let id = chat_tree - .scan_prefix(&key) - .await - .last() - // Ensure that the first message ID is always 1! - // otherwise get message id - .map_or(Ok(1), |res| { - res.map(|res| { - u64::from_be_bytes( - res.0 - .split_at(key.len()) - .1 - .try_into() - .expect("failed to convert to u64 id"), - ) - }) - })?; - - key.pop(); - key.push(7); - - batch.insert(key, id.to_be_bytes()); - } +const CHAN_KEY_LEN: usize = make_chan_key(0, 0).len(); + +define_migration!(|db| { + let chat_tree = db.open_tree(b"chat").await?; + + let mut batch = Batch::default(); + for res in chat_tree.iter().await { + let (key, val) = res?; + let mut key: Vec = key.into(); + + if key.len() == CHAN_KEY_LEN && key[8] == 8 { + deser_chan(val); + key.push(9); + + let id = chat_tree + .scan_prefix(&key) + .await + .last() + // Ensure that the first message ID is always 1! + // otherwise get message id + .map_or(Ok(1), |res| { + res.map(|res| { + u64::from_be_bytes( + res.0 + .split_at(key.len()) + .1 + .try_into() + .expect("failed to convert to u64 id"), + ) + }) + })?; + + key.pop(); + key.push(7); + + batch.insert(key, id.to_be_bytes()); } - chat_tree.apply_batch(batch).await - }; - - Box::pin(fut) -} + } + chat_tree.apply_batch(batch).await +}); diff --git a/src/db/migration/initial_db_version.rs b/src/db/migration/initial_db_version.rs index 9dd1692..b037106 100644 --- a/src/db/migration/initial_db_version.rs +++ b/src/db/migration/initial_db_version.rs @@ -1,15 +1,11 @@ use super::*; -pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { - let fut = async move { - let version_tree = db.open_tree(b"version").await?; - if !version_tree.contains_key(b"version").await? { - version_tree - .insert(b"version", &MIGRATIONS.len().to_be_bytes()) - .await?; - } - Ok(()) - }; - - Box::pin(fut) -} +define_migration!(|db| { + let version_tree = db.open_tree(b"version").await?; + if !version_tree.contains_key(b"version").await? { + version_tree + .insert(b"version", &MIGRATIONS.len().to_be_bytes()) + .await?; + } + Ok(()) +}); diff --git a/src/db/migration/mod.rs b/src/db/migration/mod.rs index ef9c236..4135358 100644 --- a/src/db/migration/mod.rs +++ b/src/db/migration/mod.rs @@ -1,26 +1,33 @@ use std::future::Future; +use bytecheck::CheckBytes; use hrpc::server::gen_prelude::BoxFuture; +use rkyv::{ + de::deserializers::SharedDeserializeMap, ser::serializers::AllocSerializer, + validation::validators::DefaultValidator, Archive, Deserialize, Serialize, +}; use tracing::Instrument; use crate::db; -use super::{Db, DbResult}; +use super::{rkyv_ser, Batch, Db, DbResult, Tree}; mod add_account_kind; mod add_next_msg_ids; mod initial_db_version; +mod proto_v2; mod remove_log_chan_id_from_admin_keys; mod timestamps_are_milliseconds; type Migration = for<'a> fn(&'a Db) -> BoxFuture<'a, DbResult<()>>; -pub const MIGRATIONS: [Migration; 5] = [ +pub const MIGRATIONS: [Migration; 6] = [ initial_db_version::migrate, add_next_msg_ids::migrate, remove_log_chan_id_from_admin_keys::migrate, add_account_kind::migrate, timestamps_are_milliseconds::migrate, + proto_v2::migrate, ]; pub async fn get_db_version(db: &Db) -> DbResult<(usize, bool)> { @@ -77,3 +84,38 @@ async fn increment_db_version(db: &Db) -> DbResult<()> { } Ok(()) } + +async fn migrate_type(tree: &Tree, prefix: &[u8], mut migrate: F) -> DbResult<()> +where + From: Archive, + To: Archive + Serialize>, + From::Archived: + for<'a> CheckBytes> + Deserialize, + To::Archived: for<'a> CheckBytes>, + F: FnMut(From) -> To, +{ + let mut batch = Batch::default(); + for res in tree.scan_prefix(prefix).await { + let (key, val) = res?; + let old = rkyv::from_bytes::(&val); + if let Ok(old) = old { + let new_val = migrate(old); + let new_val = rkyv_ser(&new_val); + batch.insert(key, new_val); + } + } + tree.apply_batch(batch).await?; + Ok(()) +} + +macro_rules! define_migration { + (|$db:ident| $e:tt) => { + pub(super) fn migrate($db: &Db) -> BoxFuture<'_, DbResult<()>> { + let fut = Box::pin(async move { $e }); + + Box::pin(fut) + } + }; +} + +pub(self) use define_migration; diff --git a/src/db/migration/proto_v2.rs b/src/db/migration/proto_v2.rs new file mode 100644 index 0000000..62d4f4e --- /dev/null +++ b/src/db/migration/proto_v2.rs @@ -0,0 +1,382 @@ +use super::*; + +use crate::{ + api::{ + chat::{ + action::{ + dropdown::Entry as DropdownEntry, Button as ButtonAction, + Dropdown as DropdownAction, Input as InputAction, Kind as ActionKind, + }, + attachment::{ImageInfo, Info}, + content::{Extra, InviteAccepted, InviteRejected, RoomUpgradedToGuild}, + embed::{Field as EmbedField, Heading as EmbedHeading}, + format::{Format as NewFormatData, *}, + overrides::Reason as NewReason, + Action, Attachment, Content as NewContent, Embed, Format as NewFormat, FormattedText, + Message as NewMessage, Minithumbnail, Overrides as NewOverrides, + Reaction as NewReaction, + }, + emote::Emote as NewEmote, + harmonytypes::{Anything as NewAnything, Empty as NewEmpty, Metadata as NewMetadata}, + profile::Profile as NewProfile, + }, + db::profile::USER_PREFIX, +}; + +use chat::Message as OldMessage; +use harmony_rust_sdk::api::profile::{user_status, UserStatus}; +use profile::{Profile as OldProfile, UserStatus as OldUserStatus}; + +define_migration!(|db| { + let chat_tree = db.open_tree(b"chat").await?; + migrate_type::(&chat_tree, &[], |old| NewMessage { + author_id: old.author_id, + content: old.content.and_then(|c| c.content).map(|old| { + let mut content = NewContent::default(); + match old { + chat::content::Content::TextMessage(text) => { + if let Some(text) = text.content { + content = content.with_text(text.text).with_text_formats( + text.format.into_iter().map(From::from).collect::>(), + ); + } + } + chat::content::Content::EmbedMessage(old) => { + content.embeds = old.embeds.into_iter().map(Into::into).collect(); + } + chat::content::Content::AttachmentMessage(old) => { + content.attachments = old.files.into_iter().map(Into::into).collect(); + } + chat::content::Content::PhotoMessage(old) => { + content.attachments = old.photos.into_iter().map(Into::into).collect(); + } + chat::content::Content::InviteRejected(old) => { + content.extra = Some(Extra::InviteRejected(old.into())); + } + chat::content::Content::InviteAccepted(old) => { + content.extra = Some(Extra::InviteAccepted(old.into())); + } + chat::content::Content::RoomUpgradedToGuild(old) => { + content.extra = Some(Extra::RoomUpgradedToGuild(old.into())); + } + } + content + }), + created_at: to_seconds(old.created_at), + edited_at: old.edited_at.map(to_seconds), + in_reply_to: old.in_reply_to, + metadata: old.metadata.map(Into::into), + overrides: old.overrides.map(Into::into), + reactions: old.reactions.into_iter().map(Into::into).collect(), + actions: Vec::new(), + }) + .await?; + let profile_tree = db.open_tree(b"profile").await?; + migrate_type::(&profile_tree, USER_PREFIX, |old| NewProfile { + user_avatar: old.user_avatar, + account_kind: old.account_kind, + user_name: old.user_name, + user_status: Some(UserStatus { + kind: (match OldUserStatus::from_i32(old.user_status).unwrap_or_default() { + OldUserStatus::Online => user_status::Kind::Online, + OldUserStatus::Idle => user_status::Kind::Idle, + OldUserStatus::DoNotDisturb => user_status::Kind::DoNotDisturb, + _ => user_status::Kind::OfflineUnspecified, + }) + .into(), + ..Default::default() + }), + }) + .await?; + Ok(()) +}); + +scherzo_derive::define_proto_mod!(before_proto_v2, harmonytypes, chat, emote, profile); + +fn to_seconds(millis: u64) -> u64 { + std::time::Duration::from_millis(millis).as_secs() +} + +impl From for Embed { + fn from(embed: chat::Embed) -> Self { + let (fields, actions) = embed.fields.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut fields, mut actions), item| { + let field = EmbedField { + title: item.title, + body: item.body.map(|f| f.text).unwrap_or_default(), + }; + fields.push(field); + actions.extend(item.actions.into_iter().map(Into::into)); + (fields, actions) + }, + ); + Embed { + header: embed.header.map(Into::into), + title: embed.title, + body: embed.body.map(Into::into), + fields, + footer: embed.footer.map(Into::into), + color: embed.color, + image: None, + actions, + } + } +} + +impl From for EmbedHeading { + fn from(old: chat::embed::EmbedHeading) -> Self { + EmbedHeading { + url: old.url, + icon: old.icon, + text: old.text, + } + } +} + +impl From for Action { + fn from(old: chat::Action) -> Self { + let info = old + .kind + .as_ref() + .map(|kind| match kind { + chat::action::Kind::Button(old) => old.data.clone(), + chat::action::Kind::Input(old) => old.data.clone(), + _ => Vec::new(), + }) + .unwrap_or_else(Vec::new); + + Action { + action_type: old.action_type, + kind: old.kind.map(Into::into), + info, + } + } +} + +impl From for ActionKind { + fn from(old: chat::action::Kind) -> Self { + match old { + chat::action::Kind::Button(old) => ActionKind::Button(ButtonAction { + text: old.text, + url: old.url, + }), + chat::action::Kind::Dropdown(old) => ActionKind::Dropdown(DropdownAction { + entries: old.entries.into_iter().map(Into::into).collect(), + label: old.label, + }), + chat::action::Kind::Input(old) => ActionKind::Input(InputAction { + label: old.label, + multiline: old.multiline, + default: None, + }), + } + } +} + +impl From for DropdownEntry { + fn from(old: chat::action::dropdown::Entry) -> Self { + DropdownEntry { + data: old.data, + label: old.label, + } + } +} + +impl From for RoomUpgradedToGuild { + fn from(old: chat::content::RoomUpgradedToGuild) -> Self { + RoomUpgradedToGuild { + upgraded_by: old.upgraded_by, + } + } +} + +impl From for InviteAccepted { + fn from(old: chat::content::InviteAccepted) -> Self { + InviteAccepted { + inviter_id: old.inviter_id, + invitee_id: old.invitee_id, + } + } +} + +impl From for InviteRejected { + fn from(old: chat::content::InviteRejected) -> Self { + InviteRejected { + inviter_id: old.inviter_id, + invitee_id: old.invitee_id, + } + } +} + +impl From for Attachment { + fn from(old: chat::Photo) -> Self { + Attachment { + id: old.hmc, + name: old.name, + // TODO: read the image for the actual mimetype + mimetype: "image/webp".to_string(), + size: old.file_size, + info: Some(Info::Image(ImageInfo { + caption: old.caption.map(|f| f.text), + height: old.height, + width: old.width, + minithumbnail: old.minithumbnail.map(Into::into), + })), + } + } +} + +impl From for Minithumbnail { + fn from(old: chat::Minithumbnail) -> Self { + Minithumbnail { + height: old.height, + width: old.width, + data: old.data, + } + } +} + +impl From for Attachment { + fn from(old: chat::Attachment) -> Self { + Attachment { + id: old.id, + name: old.name, + mimetype: old.mimetype, + size: old.size, + info: None, + } + } +} + +impl From for NewMetadata { + fn from(old: harmonytypes::Metadata) -> Self { + NewMetadata { + kind: old.kind, + extension: old + .extension + .into_iter() + .map(|(key, val)| (key, NewAnything::from(val))) + .collect(), + } + } +} + +impl From for NewAnything { + fn from(old: harmonytypes::Anything) -> Self { + NewAnything { + body: old.body, + kind: old.kind, + } + } +} + +impl From for NewEmote { + fn from(old: emote::Emote) -> Self { + NewEmote { + name: old.name, + image_id: old.image_id, + } + } +} + +impl From for NewReaction { + fn from(old: chat::Reaction) -> Self { + let emote = old.emote.unwrap_or_default(); + NewReaction { + count: old.count, + name: emote.name, + data: emote.image_id, + ..Default::default() + } + } +} + +impl From for NewOverrides { + fn from(old: chat::Overrides) -> Self { + Self { + avatar: old.avatar, + username: old.username, + reason: old.reason.map(Into::into), + } + } +} + +impl From for NewReason { + fn from(old: chat::overrides::Reason) -> Self { + match old { + chat::overrides::Reason::UserDefined(e) => NewReason::UserDefined(e), + chat::overrides::Reason::Webhook(e) => NewReason::Webhook(e.into()), + chat::overrides::Reason::SystemPlurality(e) => NewReason::SystemPlurality(e.into()), + chat::overrides::Reason::SystemMessage(e) => NewReason::SystemMessage(e.into()), + chat::overrides::Reason::Bridge(e) => NewReason::Bridge(e.into()), + } + } +} + +impl From for FormattedText { + fn from(old: chat::FormattedText) -> Self { + FormattedText { + text: old.text, + format: old.format.into_iter().map(Into::into).collect(), + } + } +} + +impl From for NewEmpty { + fn from(_: harmonytypes::Empty) -> Self { + NewEmpty {} + } +} + +impl From for NewFormat { + fn from(old: chat::Format) -> Self { + NewFormat { + length: old.length, + start: old.start, + format: old.format.and_then(Into::into), + } + } +} + +impl From for Option { + fn from(old: chat::format::Format) -> Self { + let new = match old { + chat::format::Format::Bold(_) => NewFormatData::Bold(Bold {}), + chat::format::Format::Italic(_) => NewFormatData::Italic(Italic {}), + chat::format::Format::Underline(_) => NewFormatData::Underline(Underline {}), + chat::format::Format::Monospace(_) => NewFormatData::Monospace(Monospace {}), + chat::format::Format::Superscript(_) => NewFormatData::Superscript(Superscript {}), + chat::format::Format::Subscript(_) => NewFormatData::Subscript(Subscript {}), + chat::format::Format::CodeBlock(old) => NewFormatData::CodeBlock(CodeBlock { + language: old.language, + }), + chat::format::Format::UserMention(old) => NewFormatData::UserMention(UserMention { + user_id: old.user_id, + }), + chat::format::Format::RoleMention(old) => NewFormatData::RoleMention(RoleMention { + role_id: old.role_id, + }), + chat::format::Format::ChannelMention(old) => { + NewFormatData::ChannelMention(ChannelMention { + channel_id: old.channel_id, + }) + } + chat::format::Format::GuildMention(old) => NewFormatData::GuildMention(GuildMention { + guild_id: old.guild_id, + homeserver: old.homeserver, + }), + chat::format::Format::Emoji(old) => NewFormatData::Emoji(Emoji { + emote: Some(NewEmote { + image_id: old.image_hmc, + // TODO: actually get the emote name using the pack_id from old + name: String::new(), + }), + pack_id: Some(old.pack_id), + }), + chat::format::Format::Color(old) => NewFormatData::Color(Color { kind: old.kind }), + _ => return None, + }; + Some(new) + } +} diff --git a/src/db/migration/remove_log_chan_id_from_admin_keys.rs b/src/db/migration/remove_log_chan_id_from_admin_keys.rs index ac0ce97..6518a7c 100644 --- a/src/db/migration/remove_log_chan_id_from_admin_keys.rs +++ b/src/db/migration/remove_log_chan_id_from_admin_keys.rs @@ -4,23 +4,19 @@ use crate::db::chat::ADMIN_GUILD_KEY; use super::*; -pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { - let fut = Box::pin(async move { - let chat_tree = db.open_tree(b"chat").await?; +define_migration!(|db| { + let chat_tree = db.open_tree(b"chat").await?; - if let Some(raw) = chat_tree.get(ADMIN_GUILD_KEY).await? { - let (gid_raw, rest) = raw.split_at(size_of::()); - let guild_id = unsafe { u64::from_be_bytes(gid_raw.try_into().unwrap_unchecked()) }; - let (_, cmd_raw) = rest.split_at(size_of::()); - let cmd_id = unsafe { u64::from_be_bytes(cmd_raw.try_into().unwrap_unchecked()) }; + if let Some(raw) = chat_tree.get(ADMIN_GUILD_KEY).await? { + let (gid_raw, rest) = raw.split_at(size_of::()); + let guild_id = unsafe { u64::from_be_bytes(gid_raw.try_into().unwrap_unchecked()) }; + let (_, cmd_raw) = rest.split_at(size_of::()); + let cmd_id = unsafe { u64::from_be_bytes(cmd_raw.try_into().unwrap_unchecked()) }; - let new_raw = [guild_id.to_be_bytes(), cmd_id.to_be_bytes()].concat(); + let new_raw = [guild_id.to_be_bytes(), cmd_id.to_be_bytes()].concat(); - chat_tree.insert(ADMIN_GUILD_KEY, new_raw).await?; - } + chat_tree.insert(ADMIN_GUILD_KEY, new_raw).await?; + } - Ok(()) - }); - - Box::pin(fut) -} + Ok(()) +}); diff --git a/src/db/migration/timestamps_are_milliseconds.rs b/src/db/migration/timestamps_are_milliseconds.rs index ecd680e..50479c1 100644 --- a/src/db/migration/timestamps_are_milliseconds.rs +++ b/src/db/migration/timestamps_are_milliseconds.rs @@ -1,8 +1,6 @@ use super::*; -use db::{ - rkyv_ser, Batch -}; +use db::{rkyv_ser, Batch}; use harmony_rust_sdk::api::chat::Message as HarmonyMessage; pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { diff --git a/src/db/mod.rs b/src/db/mod.rs index f5ab4e7..f8fa561 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -36,7 +36,7 @@ use std::{ use crate::{config::DbConfig, utils::evec::EVec, ServerError, ServerResult}; use crate::api::{ - chat::{Channel, Guild, Invite, Message as HarmonyMessage, Role}, + chat::{Channel, Guild, Invite, Message as HarmonyMessage, PrivateChannel, Role}, emote::{Emote, EmotePack}, profile::Profile, }; @@ -46,6 +46,8 @@ use rkyv::{ }; use tracing::Instrument; +use self::chat::UserPendingInvites; + pub mod migration; #[cfg(feature = "sled")] pub mod sled; @@ -92,6 +94,11 @@ impl Batch { pub fn remove(&mut self, key: impl Into) { self.inserts.push((key.into(), None)); } + + pub fn merge(mut self, other: Batch) -> Self { + self.inserts.extend(other.inserts); + self + } } #[derive(Debug)] @@ -99,6 +106,12 @@ pub struct DbError { pub inner: Box, } +impl From for DbError { + fn from(err: anyhow::Error) -> Self { + Self { inner: err.into() } + } +} + impl Display for DbError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("a database error occured") @@ -118,13 +131,13 @@ pub fn make_u64_iter_logic(raw: &[u8]) -> impl Iterator + '_ { } pub mod profile { - use super::concat_static; + use super::concat_arrs; pub const USER_PREFIX: &[u8] = b"user_"; pub const FOREIGN_PREFIX: &[u8] = b"fuser_"; pub const fn make_local_to_foreign_user_key(local_id: u64) -> [u8; 15] { - concat_static(&[FOREIGN_PREFIX, &local_id.to_be_bytes(), &[2]]) + concat_arrs(&[FOREIGN_PREFIX, &local_id.to_be_bytes(), &[2]]) } pub fn make_foreign_to_local_user_key(foreign_id: u64, host: &str) -> Vec { @@ -138,11 +151,11 @@ pub mod profile { } pub const fn make_user_profile_key(user_id: u64) -> [u8; 13] { - concat_static(&[USER_PREFIX, &user_id.to_be_bytes()]) + concat_arrs(&[USER_PREFIX, &user_id.to_be_bytes()]) } pub fn make_user_metadata_prefix(user_id: u64) -> [u8; 14] { - concat_static(&[make_user_profile_key(user_id).as_ref(), [1].as_ref()]) + concat_arrs(&[make_user_profile_key(user_id).as_ref(), [1].as_ref()]) } pub fn make_user_metadata_key(user_id: u64, app_id: &str) -> Vec { @@ -155,12 +168,12 @@ pub mod profile { } pub mod emote { - use super::{concat_static, profile::USER_PREFIX}; + use super::{concat_arrs, profile::USER_PREFIX}; pub const EMOTEPACK_PREFIX: &[u8] = b"emotep_"; pub const fn make_emote_pack_key(pack_id: u64) -> [u8; 15] { - concat_static(&[EMOTEPACK_PREFIX, &pack_id.to_be_bytes()]) + concat_arrs(&[EMOTEPACK_PREFIX, &pack_id.to_be_bytes()]) } pub fn make_emote_pack_emote_key(pack_id: u64, image_id: &str) -> Vec { @@ -173,22 +186,41 @@ pub mod emote { } pub const fn make_equipped_emote_prefix(user_id: u64) -> [u8; 14] { - concat_static(&[USER_PREFIX, &user_id.to_be_bytes(), &[9]]) + concat_arrs(&[USER_PREFIX, &user_id.to_be_bytes(), &[9]]) } pub const fn make_equipped_emote_key(user_id: u64, pack_id: u64) -> [u8; 22] { - concat_static(&[&make_equipped_emote_prefix(user_id), &pack_id.to_be_bytes()]) + concat_arrs(&[&make_equipped_emote_prefix(user_id), &pack_id.to_be_bytes()]) } } pub mod chat { - use super::concat_static; + use harmony_rust_sdk::api::chat::PendingInvite; + + use super::concat_arrs; + pub const DM_WITH_USER_PREFIX: &[u8] = b"dm_with_"; + pub const PENDING_INVITES_PREFIX: &[u8] = b"pending_invites_"; pub const INVITE_PREFIX: &[u8] = b"invite_"; + pub const PRIV_INVITE_PREFIX: &[u8] = b"priv_invite_"; + pub const PRIV_CHANNEL_PREFIX: &[u8] = b"priv_"; pub const ADMIN_GUILD_KEY: &[u8] = b"admin_guild_key_data"; // perms + #[inline(always)] + pub const fn make_guild_key(guild_id: u64) -> [u8; 8] { + guild_id.to_be_bytes() + } + + pub const fn make_pc_key(channel_id: u64) -> [u8; 13] { + concat_arrs(&[PRIV_CHANNEL_PREFIX, &channel_id.to_be_bytes()]) + } + + pub const fn make_pc_creator_key(channel_id: u64) -> [u8; 14] { + concat_arrs(&[&make_pc_key(channel_id), &[5]]) + } + pub fn make_guild_perm_key(guild_id: u64, role_id: u64, matches: &str) -> Vec { [ &make_role_guild_perms_prefix(guild_id, role_id), @@ -211,7 +243,7 @@ pub mod chat { } pub const fn make_role_guild_perms_prefix(guild_id: u64, role_id: u64) -> [u8; 18] { - concat_static(&[ + concat_arrs(&[ &make_guild_role_prefix(guild_id), &role_id.to_be_bytes(), &[9], @@ -219,11 +251,11 @@ pub mod chat { } pub const fn make_guild_role_key(guild_id: u64, role_id: u64) -> [u8; 17] { - concat_static(&[&make_guild_role_prefix(guild_id), &role_id.to_be_bytes()]) + concat_arrs(&[&make_guild_role_prefix(guild_id), &role_id.to_be_bytes()]) } pub const fn make_guild_user_roles_key(guild_id: u64, user_id: u64) -> [u8; 17] { - concat_static(&[ + concat_arrs(&[ &make_guild_user_roles_prefix(guild_id), &user_id.to_be_bytes(), ]) @@ -233,12 +265,34 @@ pub mod chat { // message - pub const fn make_pinned_msgs_key(guild_id: u64, channel_id: u64) -> [u8; 18] { - concat_static(&[&make_chan_key(guild_id, channel_id), &[6]]) + pub const fn make_pinned_msgs_key_guild(guild_id: u64, channel_id: u64) -> [u8; 18] { + concat_arrs(&[&make_chan_key(guild_id, channel_id), &[6]]) + } + + pub const fn make_pinned_msgs_key_pc(channel_id: u64) -> [u8; 14] { + concat_arrs(&[&make_pc_key(channel_id), &[6]]) + } + + pub fn make_pinned_msgs_key(guild_id: Option, channel_id: u64) -> Vec { + guild_id.map_or_else( + || make_pinned_msgs_key_pc(channel_id).to_vec(), + |guild_id| make_pinned_msgs_key_guild(guild_id, channel_id).to_vec(), + ) } - pub const fn make_next_msg_id_key(guild_id: u64, channel_id: u64) -> [u8; 18] { - concat_static(&[&make_chan_key(guild_id, channel_id), &[7]]) + pub const fn make_next_msg_id_key_guild(guild_id: u64, channel_id: u64) -> [u8; 18] { + concat_arrs(&[&make_chan_key(guild_id, channel_id), &[7]]) + } + + pub const fn make_next_msg_id_key_pc(channel_id: u64) -> [u8; 14] { + concat_arrs(&[&make_pc_key(channel_id), &[7]]) + } + + pub fn make_next_msg_id_key(guild_id: Option, channel_id: u64) -> Vec { + guild_id.map_or_else( + || make_next_msg_id_key_pc(channel_id).to_vec(), + |guild_id| make_next_msg_id_key_guild(guild_id, channel_id).to_vec(), + ) } pub const fn make_role_channel_perms_prefix( @@ -246,36 +300,66 @@ pub mod chat { channel_id: u64, role_id: u64, ) -> [u8; 26] { - concat_static(&[ + concat_arrs(&[ &make_chan_key(guild_id, channel_id), &[8], &role_id.to_be_bytes(), ]) } + pub const fn make_msg_prefix_pc(channel_id: u64) -> [u8; 13] { + concat_arrs(&[&make_pc_key(channel_id), &[9]]) + } + pub const fn make_msg_prefix(guild_id: u64, channel_id: u64) -> [u8; 18] { - concat_static(&[&make_chan_key(guild_id, channel_id), &[9]]) + concat_arrs(&[&make_chan_key(guild_id, channel_id), &[9]]) + } + + pub const fn make_msg_key_pc(channel_id: u64, message_id: u64) -> [u8; 21] { + concat_arrs(&[&make_msg_prefix_pc(channel_id), &message_id.to_be_bytes()]) } - pub const fn make_msg_key(guild_id: u64, channel_id: u64, message_id: u64) -> [u8; 26] { - concat_static(&[ + pub const fn make_msg_key_guild(guild_id: u64, channel_id: u64, message_id: u64) -> [u8; 26] { + concat_arrs(&[ &make_msg_prefix(guild_id, channel_id), &message_id.to_be_bytes(), ]) } + pub fn make_msg_key(guild_id: Option, channel_id: u64, message_id: u64) -> Vec { + guild_id.map_or_else( + || make_msg_key_pc(channel_id, message_id).to_vec(), + |guild_id| make_msg_key_guild(guild_id, channel_id, message_id).to_vec(), + ) + } + + pub fn make_user_reacted_msg_key_pc( + channel_id: u64, + message_id: u64, + user_id: u64, + data: &str, + ) -> Vec { + [ + make_msg_key_pc(channel_id, message_id).as_ref(), + &[0], + user_id.to_be_bytes().as_ref(), + data.as_bytes(), + ] + .concat() + } + pub fn make_user_reacted_msg_key( guild_id: u64, channel_id: u64, message_id: u64, user_id: u64, - image_id: &str, + data: &str, ) -> Vec { [ - make_msg_key(guild_id, channel_id, message_id).as_ref(), + make_msg_key_guild(guild_id, channel_id, message_id).as_ref(), &[0], user_id.to_be_bytes().as_ref(), - image_id.as_bytes(), + data.as_bytes(), ] .concat() } @@ -284,12 +368,20 @@ pub mod chat { // member + pub const fn make_pc_mem_prefix(channel_id: u64) -> [u8; 14] { + concat_arrs(&[&make_pc_key(channel_id), &[8]]) + } + + pub const fn make_member_key_pc(channel_id: u64, user_id: u64) -> [u8; 22] { + concat_arrs(&[&make_pc_mem_prefix(channel_id), &user_id.to_be_bytes()]) + } + pub const fn make_member_key(guild_id: u64, user_id: u64) -> [u8; 17] { - concat_static(&[&make_guild_mem_prefix(guild_id), &user_id.to_be_bytes()]) + concat_arrs(&[&make_guild_mem_prefix(guild_id), &user_id.to_be_bytes()]) } pub const fn make_banned_member_key(guild_id: u64, user_id: u64) -> [u8; 17] { - concat_static(&[ + concat_arrs(&[ &make_guild_banned_mem_prefix(guild_id), &user_id.to_be_bytes(), ]) @@ -300,35 +392,39 @@ pub mod chat { // guild pub const fn make_guild_mem_prefix(guild_id: u64) -> [u8; 9] { - concat_static(&[&guild_id.to_be_bytes(), &[9]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[9]]) } pub const fn make_guild_chan_prefix(guild_id: u64) -> [u8; 9] { - concat_static(&[&guild_id.to_be_bytes(), &[8]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[8]]) } pub const fn make_guild_banned_mem_prefix(guild_id: u64) -> [u8; 9] { - concat_static(&[&guild_id.to_be_bytes(), &[7]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[7]]) } pub const fn make_guild_role_prefix(guild_id: u64) -> [u8; 9] { - concat_static(&[&guild_id.to_be_bytes(), &[5]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[5]]) } pub const fn make_guild_user_roles_prefix(guild_id: u64) -> [u8; 9] { - concat_static(&[&guild_id.to_be_bytes(), &[4]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[4]]) + } + + pub const fn make_pc_list_key_prefix(user_id: u64) -> [u8; 10] { + concat_arrs(&[&user_id.to_be_bytes(), &[1, 4]]) } pub const fn make_guild_role_ordering_key(guild_id: u64) -> [u8; 10] { - concat_static(&[&guild_id.to_be_bytes(), &[1, 3]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[1, 3]]) } pub const fn make_guild_list_key_prefix(user_id: u64) -> [u8; 10] { - concat_static(&[&user_id.to_be_bytes(), &[1, 2]]) + concat_arrs(&[&user_id.to_be_bytes(), &[1, 2]]) } pub const fn make_guild_chan_ordering_key(guild_id: u64) -> [u8; 10] { - concat_static(&[&guild_id.to_be_bytes(), &[1, 1]]) + concat_arrs(&[&guild_id.to_be_bytes(), &[1, 1]]) } pub fn make_guild_list_key(user_id: u64, guild_id: u64, host: &str) -> Vec { @@ -340,19 +436,55 @@ pub mod chat { .concat() } + pub fn make_pc_list_key(user_id: u64, channel_id: u64, host: &str) -> Vec { + [ + make_pc_list_key_prefix(user_id).as_ref(), + channel_id.to_be_bytes().as_ref(), + host.as_bytes(), + ] + .concat() + } + // guild pub const fn make_chan_key(guild_id: u64, channel_id: u64) -> [u8; 17] { - concat_static(&[&make_guild_chan_prefix(guild_id), &channel_id.to_be_bytes()]) + concat_arrs(&[&make_guild_chan_prefix(guild_id), &channel_id.to_be_bytes()]) } pub fn make_invite_key(name: &str) -> Vec { [INVITE_PREFIX, name.as_bytes()].concat() } + + pub fn make_priv_invite_key(name: &str) -> Vec { + [PRIV_INVITE_PREFIX, name.as_bytes()].concat() + } + + pub fn make_priv_invite_allowed_key(name: &str) -> Vec { + [&make_priv_invite_key(name), "_allowed_users".as_bytes()].concat() + } + + pub const fn make_dm_with_user_key(user_id: u64, other_user_id: u64) -> [u8; 25] { + concat_arrs(&[ + DM_WITH_USER_PREFIX, + &user_id.to_be_bytes(), + &[b'_'], + &other_user_id.to_be_bytes(), + ]) + } + + #[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Default, Debug)] + #[archive_attr(derive(bytecheck::CheckBytes))] + pub struct UserPendingInvites { + pub invites: Vec, + } + + pub const fn make_user_pending_invites_key(user_id: u64) -> [u8; 24] { + concat_arrs(&[PENDING_INVITES_PREFIX, &user_id.to_be_bytes()]) + } } pub mod auth { - use super::concat_static; + use super::concat_arrs; pub const ATIME_PREFIX: &[u8] = b"atime_"; pub const TOKEN_PREFIX: &[u8] = b"token_"; @@ -364,11 +496,11 @@ pub mod auth { } pub const fn token_key(user_id: u64) -> [u8; 14] { - concat_static(&[TOKEN_PREFIX, &user_id.to_be_bytes()]) + concat_arrs(&[TOKEN_PREFIX, &user_id.to_be_bytes()]) } pub const fn atime_key(user_id: u64) -> [u8; 14] { - concat_static(&[ATIME_PREFIX, &user_id.to_be_bytes()]) + concat_arrs(&[ATIME_PREFIX, &user_id.to_be_bytes()]) } pub fn single_use_token_key(token_hashed: &[u8]) -> Vec { @@ -384,15 +516,22 @@ pub mod sync { } } +pub async fn create_batch_delete_prefix( + tree: &Tree, + prefix: impl AsRef<[u8]>, +) -> ServerResult { + tree.scan_prefix(prefix.as_ref()) + .await + .try_fold(Batch::default(), |mut batch, res| { + let (key, _) = res.map_err(ServerError::from)?; + batch.remove(key); + ServerResult::Ok(batch) + }) + .map_err(Into::into) +} + pub async fn batch_delete_prefix(tree: &Tree, prefix: impl AsRef<[u8]>) -> ServerResult<()> { - let batch = - tree.scan_prefix(prefix.as_ref()) - .await - .try_fold(Batch::default(), |mut batch, res| { - let (key, _) = res.map_err(ServerError::from)?; - batch.remove(key); - ServerResult::Ok(batch) - })?; + let batch = create_batch_delete_prefix(tree, prefix).await?; tree.apply_batch(batch).await?; Ok(()) } @@ -410,6 +549,8 @@ crate::impl_deser! { role, Role; emote, Emote; emote_pack, EmotePack; + private_channel, PrivateChannel; + pending_invites, UserPendingInvites; } pub fn deser_invite_entry_guild_id(data: &[u8]) -> u64 { @@ -440,8 +581,9 @@ macro_rules! impl_deser { }; } -const fn concat_static(arrs: &[&[u8]]) -> [u8; LEN] { +const fn concat_arrs(arrs: &[&[u8]]) -> [u8; LEN] { // check if the lengths match. + #[cfg(debug_assertions)] { let mut new_len = 0; let mut arr_index = 0; @@ -464,7 +606,7 @@ const fn concat_static(arrs: &[&[u8]]) -> [u8; LEN] { // SAFETY: // - the pointers dont overlap because we use our own allocated array // - the pointers are guaranteed to be not null (we create one in fn body - // others are gotten as references + // others are gotten as references) // - the pointers are guaranteed to be aligned (are they?) unsafe { std::ptr::copy_nonoverlapping(arr.as_ptr(), new.as_mut_ptr().add(padding), arr.len()); diff --git a/src/db/sled.rs b/src/db/sled.rs index b86aad0..45f436a 100644 --- a/src/db/sled.rs +++ b/src/db/sled.rs @@ -10,6 +10,7 @@ type SledFut = Ready>; pub mod shared { use hrpc::common::future::ready; + use smol_str::SmolStr; use super::*; @@ -63,7 +64,10 @@ pub mod shared { self.inner .open_tree(name) .map_err(Into::into) - .map(|tree| Tree { inner: tree }), + .map(|tree| Tree { + inner: tree, + name: String::from_utf8_lossy(name).as_ref().into(), + }), ) } @@ -79,9 +83,14 @@ pub mod shared { #[derive(Debug, Clone)] pub struct Tree { inner: sled::Tree, + name: SmolStr, } impl Tree { + pub fn name(&self) -> &str { + self.name.as_str() + } + pub fn get(&self, key: &[u8]) -> SledFut> { ready( self.inner diff --git a/src/error.rs b/src/error.rs index d3ecfb9..d816118 100644 --- a/src/error.rs +++ b/src/error.rs @@ -54,15 +54,20 @@ pub enum ServerError { guild_id: u64, user_id: u64, }, + UserNotInPrivateChannel { + channel_id: u64, + user_id: u64, + }, Unauthenticated, NotImplemented, NoSuchMessage { - guild_id: u64, + guild_id: Option, channel_id: u64, message_id: u64, }, GuildAlreadyExists(u64), NoSuchGuild(u64), + NoSuchPrivateChannel(u64), ChannelAlreadyExists { guild_id: u64, channel_id: u64, @@ -128,6 +133,7 @@ pub enum ServerError { InvalidEmailConfig(toml::de::Error), FailedToFetchLink(reqwest::Error), FailedToDownload(reqwest::Error), + ImageProcessError(image::ImageError), } impl StdError for ServerError { @@ -144,6 +150,7 @@ impl StdError for ServerError { ServerError::FailedToFetchLink(err) => Some(err), ServerError::FailedToDownload(err) => Some(err), ServerError::InvalidEmailConfig(err) => Some(err), + ServerError::ImageProcessError(err) => Some(err), _ => None, } } @@ -153,6 +160,7 @@ impl Display for ServerError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { travel_error(f, self); match self { + ServerError::ImageProcessError(_) => f.write_str("could not process image"), ServerError::InvalidEmailConfig(_) => f.write_str("invalid email config"), ServerError::InvalidProtoMessage(err) => { write!(f, "couldn't decode a response body: {}", err) @@ -190,6 +198,12 @@ impl Display for ServerError { ServerError::UserNotInGuild { guild_id, user_id } => { write!(f, "user {} not in guild {}", user_id, guild_id) } + ServerError::UserNotInPrivateChannel { + channel_id, + user_id, + } => { + write!(f, "user {} not in private channel {}", user_id, channel_id) + } ServerError::UserAlreadyExists => f.write_str("user already exists"), ServerError::UserAlreadyInGuild => f.write_str("user already in guild"), ServerError::Unauthenticated => f.write_str("invalid-session"), @@ -217,6 +231,9 @@ impl Display for ServerError { write!(f, "guild {} already exists", guild_id) } ServerError::NoSuchGuild(id) => write!(f, "no such guild with id {}", id), + ServerError::NoSuchPrivateChannel(id) => { + write!(f, "no such private channel with id {}", id) + } ServerError::NoSuchUser(id) => write!(f, "no such user with id {}", id), ServerError::NoSuchMessage { guild_id, @@ -224,7 +241,7 @@ impl Display for ServerError { message_id, } => write!( f, - "no such message {} in channel {} in guild {}", + "no such message {} in channel {} in guild {:?}", message_id, channel_id, guild_id ), ServerError::NoSuchInvite(id) => write!(f, "no such invite with id {}", id), @@ -365,7 +382,9 @@ impl ServerError { ) | ServerError::MustNotBeLastOwner | ServerError::ContentCantBeSentByUser - | ServerError::InvalidProtoMessage(_) => StatusCode::BAD_REQUEST, + | ServerError::InvalidProtoMessage(_) + | ServerError::NoSuchPrivateChannel(_) + | ServerError::UserNotInPrivateChannel { .. } => StatusCode::BAD_REQUEST, ServerError::FederationDisabled | ServerError::HostNotAllowed => StatusCode::FORBIDDEN, ServerError::IoError(_) | ServerError::InternalServerError @@ -378,7 +397,8 @@ impl ServerError { | ServerError::MultipartError(_) | ServerError::InvalidEmailConfig(_) | ServerError::FailedToFetchLink(_) - | ServerError::FailedToDownload(_) => StatusCode::INTERNAL_SERVER_ERROR, + | ServerError::FailedToDownload(_) + | ServerError::ImageProcessError(_) => StatusCode::INTERNAL_SERVER_ERROR, ServerError::TooFast(_) => StatusCode::TOO_MANY_REQUESTS, ServerError::MediaNotFound | ServerError::LinkNotFound(_) => StatusCode::NOT_FOUND, ServerError::NotImplemented => StatusCode::NOT_IMPLEMENTED, @@ -399,7 +419,8 @@ impl ServerError { | ServerError::InvalidProtoMessage(_) | ServerError::InvalidEmailConfig(_) | ServerError::FailedToFetchLink(_) - | ServerError::FailedToDownload(_) => HrpcErrorIdentifier::InternalServerError.as_id(), + | ServerError::FailedToDownload(_) + | ServerError::ImageProcessError(_) => HrpcErrorIdentifier::InternalServerError.as_id(), ServerError::Unauthenticated => "h.blank-session", ServerError::InvalidAuthId => "h.bad-auth-id", ServerError::UserAlreadyExists => "h.already-registered", @@ -421,7 +442,9 @@ impl ServerError { ServerError::WrongStep { .. } => "h.bad-auth-choice", ServerError::WrongTypeForField { .. } => "h.missing-form", ServerError::WrongEmailOrPassword { .. } => "h.bad-password\nh.bad-email", - ServerError::UserNotInGuild { .. } => "h.not-joined", + ServerError::UserNotInGuild { .. } | ServerError::UserNotInPrivateChannel { .. } => { + "h.not-joined" + } ServerError::NotImplemented => HrpcErrorIdentifier::NotImplemented.as_id(), ServerError::ChannelAlreadyExists { .. } => "h.channel-already-exists", ServerError::GuildAlreadyExists(_) => "h.guild-already-exists", @@ -429,6 +452,7 @@ impl ServerError { ServerError::NoSuchChannel { .. } => "h.bad-channel-id", ServerError::UnderSpecifiedChannels => "h.underspecified-channels", ServerError::NoSuchGuild(_) => "h.bad-guild-id", + ServerError::NoSuchPrivateChannel(_) => "h.bad-private-channel-id", ServerError::NoSuchInvite(_) | ServerError::InviteNameEmpty => "h.bad-invite-id", ServerError::NoSuchUser(_) => "h.bad-user-id", ServerError::SessionExpired => "h.bad-session", @@ -484,12 +508,7 @@ impl ServerError { let status = self.status(); let err = HrpcError::from(self); - http::Response::builder() - .status(status) - .header(version_header_name(), version_header_value()) - .header(http::header::CONTENT_TYPE, content_header_value()) - .body(box_body(Body::full(encode_protobuf_message(&err).freeze()))) - .unwrap() + hrpc_error_response(err, status) } pub fn into_rest_http_response(self) -> HttpResponse { @@ -500,6 +519,15 @@ impl ServerError { } } +pub fn hrpc_error_response(err: HrpcError, status: StatusCode) -> HttpResponse { + http::Response::builder() + .status(status) + .header(version_header_name(), version_header_value()) + .header(http::header::CONTENT_TYPE, content_header_value()) + .body(box_body(Body::full(encode_protobuf_message(&err).freeze()))) + .unwrap() +} + pub fn rest_error_response(mut msg: String, status: StatusCode) -> HttpResponse { msg.insert_str(0, "{ message: \""); msg.push_str("\" }"); diff --git a/src/impls/auth/begin_auth.rs b/src/impls/auth/begin_auth.rs index aaefe2c..5795030 100644 --- a/src/impls/auth/begin_auth.rs +++ b/src/impls/auth/begin_auth.rs @@ -12,7 +12,7 @@ pub async fn handler( .and_modify(|s| *s = vec![initial_auth_step()]) .or_insert_with(|| vec![initial_auth_step()]); - tracing::debug!("new auth session {}", auth_id); + tracing::debug!("new auth session {auth_id}"); Ok((BeginAuthResponse { auth_id: auth_id.into(), diff --git a/src/impls/auth/check_logged_in.rs b/src/impls/auth/check_logged_in.rs index fb00c7e..48fcfec 100644 --- a/src/impls/auth/check_logged_in.rs +++ b/src/impls/auth/check_logged_in.rs @@ -5,5 +5,5 @@ pub async fn handler( request: Request, ) -> Result, HrpcServerError> { svc.deps.auth(&request).await?; - Ok((CheckLoggedInResponse {}).into_response()) + Ok(CheckLoggedInResponse::new().into_response()) } diff --git a/src/impls/auth/delete_user.rs b/src/impls/auth/delete_user.rs index c2f3961..333159e 100644 --- a/src/impls/auth/delete_user.rs +++ b/src/impls/auth/delete_user.rs @@ -15,8 +15,7 @@ pub async fn logic(deps: &Dependencies, user_id: u64) -> ServerResult<()> { user_id, Some("Deleted User".to_string()), Some("".to_string()), - Some(UserStatus::OfflineUnspecified.into()), - Some(false), + Some(UserStatus::default()), ) .await?; diff --git a/src/impls/auth/login_federated.rs b/src/impls/auth/login_federated.rs index ce215cc..04554a4 100644 --- a/src/impls/auth/login_federated.rs +++ b/src/impls/auth/login_federated.rs @@ -47,8 +47,7 @@ pub async fn handler( ); // Add the profile entry let profile = Profile { - is_bot: false, - user_status: UserStatus::OfflineUnspecified.into(), + user_status: Some(UserStatus::default()), user_avatar: avatar, user_name: username, account_kind: AccountKind::FullUnspecified.into(), diff --git a/src/impls/auth/mod.rs b/src/impls/auth/mod.rs index a785918..ed0c5c3 100644 --- a/src/impls/auth/mod.rs +++ b/src/impls/auth/mod.rs @@ -6,6 +6,7 @@ use crate::api::{ }; use ahash::RandomState; use dashmap::DashMap; +use harmony_rust_sdk::api::profile::AccountKind; use hrpc::server::gen_prelude::BoxFuture; use hyper::{http, HeaderMap}; use rand::{Rng, SeedableRng}; @@ -142,10 +143,10 @@ impl AuthServer { atime_key(id), get_time_secs().to_be_bytes(), ); - } else if !profile.is_bot + } else if profile.account_kind() != AccountKind::Bot && auth_how_old >= SESSION_EXPIRE { - tracing::debug!("user {} session has expired", id); + tracing::debug!("user {id} session has expired"); batch.remove(auth_key); batch.remove(token_key(id)); batch.remove(atime_key(id)); @@ -160,11 +161,11 @@ impl AuthServer { .await .map_err(ServerError::DbError) { - tracing::error!("error applying auth token batch: {}", err); + tracing::error!("error applying auth token batch: {err}"); } } Err(err) => { - tracing::error!("error scanning tree for tokens: {}", err); + tracing::error!("error scanning tree for tokens: {err}"); } } diff --git a/src/impls/auth/next_step.rs b/src/impls/auth/next_step.rs index 21f8bcd..d914a43 100644 --- a/src/impls/auth/next_step.rs +++ b/src/impls/auth/next_step.rs @@ -80,7 +80,7 @@ pub async fn handler( }); }; - tracing::debug!("handling form '{}'", title); + tracing::debug!("handling form '{title}'"); let mut values = Vec::with_capacity(fields.len()); @@ -120,9 +120,9 @@ pub async fn handler( drop(step_stack); if let Some(chan) = svc.send_step.get(auth_id.as_str()) { - tracing::debug!("sending next step: {:?}", next_step); + tracing::debug!("sending next step: {next_step:?}"); if let Err(err) = chan.send(next_step.clone()).await { - tracing::error!("failed to send auth step: {}", err); + tracing::error!("failed to send auth step: {err}"); } } else { tracing::debug!("no stream found, pushing to queue"); @@ -159,7 +159,7 @@ fn form<'a>( .map(|(name, r#type)| { auth_step::form::FormField::new(name.to_string(), r#type.to_string()) }) - .collect(), + .collect::>(), ))) } @@ -180,7 +180,7 @@ pub fn handle_choice(svc: &AuthServer, choice: &str) -> ServerResult { AuthStep { can_go_back: true, fallback_url: String::default(), - step: Some(auth_step::Step::new_choice(auth_step::Choice::new( + step: Some(auth_step::Step::Choice(auth_step::Choice::new( "other-options".to_string(), options, ))), @@ -217,10 +217,7 @@ pub fn handle_choice(svc: &AuthServer, choice: &str) -> ServerResult { step: form("register", fields), } } - choice => bail!(( - "h.invalid-choice", - format!("got invalid choice: {}", choice), - )), + choice => bail!(("h.invalid-choice", format!("got invalid choice: {choice}"),)), }; Ok(step) @@ -292,7 +289,7 @@ pub fn handle_fields( } field => bail!(( "h.invalid-field-type", - format!("got invalid field type: {}", field) + format!("got invalid field type: {field}") )), } } diff --git a/src/impls/auth/next_step/email.rs b/src/impls/auth/next_step/email.rs index 1e26849..d8d8b5a 100644 --- a/src/impls/auth/next_step/email.rs +++ b/src/impls/auth/next_step/email.rs @@ -38,7 +38,7 @@ pub async fn send_token_email( ), ];*/ - let subject = format!("Harmony - {} for {}", action, &deps.config.host); + let subject = format!("Harmony - {action} for {}", &deps.config.host); send_email(deps, to, subject, plain_body, None, Vec::new()).await?; diff --git a/src/impls/auth/next_step/login.rs b/src/impls/auth/next_step/login.rs index eaa2e28..1cb5417 100644 --- a/src/impls/auth/next_step/login.rs +++ b/src/impls/auth/next_step/login.rs @@ -35,7 +35,7 @@ pub async fn handle(svc: &AuthServer, values: &mut Vec) -> ServerResult { - tracing::debug!("received auth step to send to id {}", auth_id); + tracing::debug!("received auth step to send to id {auth_id}"); let end_stream = matches!( step, AuthStep { @@ -59,9 +55,7 @@ pub async fn handler( .await { tracing::error!( - "error occured while sending step to id {}: {}", - auth_id, - err + "error occured while sending step to id {auth_id}: {err}" ); // Break from loop since we errored @@ -84,7 +78,7 @@ pub async fn handler( } svc.send_step.remove(&auth_id); - tracing::debug!("removing stream for id {}", auth_id); + tracing::debug!("removing stream for id {auth_id}"); error.map_or(Ok(()), |err| Err(err.into())) } diff --git a/src/impls/batch/batch.rs b/src/impls/batch/batch.rs deleted file mode 100644 index e987af9..0000000 --- a/src/impls/batch/batch.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::*; - -pub async fn handler( - svc: &BatchServer, - mut request: Request, -) -> ServerResult> { - let auth_header = request - .header_map_mut() - .and_then(|h| h.remove(header::AUTHORIZATION)); - let BatchRequest { requests } = request.into_message().await?; - - let request_len = requests.len(); - let (bodies, endpoints) = requests.into_iter().fold( - ( - Vec::with_capacity(request_len), - Vec::with_capacity(request_len), - ), - |(mut bodies, mut endpoints), request| { - bodies.push(request.request); - endpoints.push(request.endpoint); - (bodies, endpoints) - }, - ); - let responses = svc - .make_req(bodies, Endpoint::Different(endpoints), auth_header) - .await?; - - Ok((BatchResponse { responses }).into_response()) -} diff --git a/src/impls/batch/batch_same.rs b/src/impls/batch/batch_same.rs deleted file mode 100644 index 6863412..0000000 --- a/src/impls/batch/batch_same.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::*; - -pub async fn handler( - svc: &BatchServer, - mut request: Request, -) -> ServerResult> { - let auth_header = request - .header_map_mut() - .and_then(|h| h.remove(header::AUTHORIZATION)); - let BatchSameRequest { endpoint, requests } = request.into_message().await?; - - let responses = svc - .make_req(requests, Endpoint::Same(endpoint), auth_header) - .await?; - - Ok((BatchSameResponse { responses }).into_response()) -} diff --git a/src/impls/batch/mod.rs b/src/impls/batch/mod.rs deleted file mode 100644 index cb312f6..0000000 --- a/src/impls/batch/mod.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{future::Future, ops::Not}; - -use crate::api::{ - batch::{batch_service_server::BatchService, *}, - exports::{ - hrpc::{ - response, - server::{router::RoutesFinalized, MakeRoutes}, - }, - prost::bytes::Bytes, - }, -}; -use hrpc::{body::Body, exports::futures_util::StreamExt}; -use hyper::{header, http::HeaderValue}; -use swimmer::{Pool, PoolBuilder, Recyclable}; -use tower::Service as _; - -use super::prelude::*; - -#[allow(clippy::module_inception)] -pub mod batch; -pub mod batch_same; - -struct BatchReq { - bodies: Vec, - endpoint: Endpoint, - auth_header: Option, -} - -impl BatchReq { - async fn process_req( - self, - service: &mut RoutesFinalized, - ) -> Result, HrpcServerError> { - fn process_request( - body: Bytes, - endpoint: &str, - auth_header: &Option, - service: &mut RoutesFinalized, - ) -> impl Future> + Send + 'static { - let mut req = Request::new_with_body(Body::full(body)); - *req.endpoint_mut() = endpoint.to_string().into(); - - if let Some(auth) = auth_header { - req.get_or_insert_header_map() - .insert(header::AUTHORIZATION, auth.clone()); - } - - let fut = service.call(req); - - async move { - let mut reply = fut.await.unwrap(); - if let Some(err) = reply.extensions_mut().remove::() { - bail!(err); - } - - // This should be safe since we know we insert a message into responses - // as whole chunks - Ok(response::Parts::from(reply).body.next().await.unwrap()?) - } - } - - if self.bodies.len() > 64 { - return Err(ServerError::TooManyBatchedRequests.into()); - } - - let mut responses = Vec::with_capacity(self.bodies.len()); - - let auth_header = &self.auth_header; - match &self.endpoint { - Endpoint::Same(endpoint) => { - tracing::debug!( - "batching {} requests for endpoint {}", - self.bodies.len(), - endpoint - ); - if is_valid_endpoint(endpoint).not() { - bail!(ServerError::InvalidBatchEndpoint(endpoint.to_string())); - } - let mut handles = Vec::with_capacity(self.bodies.len()); - for body in self.bodies { - let handle = - tokio::spawn(process_request(body, endpoint, auth_header, service)); - handles.push(handle); - } - for handle in handles { - responses.push(handle.await.expect("task panicked")?); - } - } - Endpoint::Different(a) => { - for endpoint in a { - if is_valid_endpoint(endpoint).not() { - bail!(ServerError::InvalidBatchEndpoint(endpoint.to_string())); - } - } - let mut handles = Vec::with_capacity(self.bodies.len()); - for (body, endpoint) in self.bodies.into_iter().zip(a) { - tracing::debug!("batching request for endpoint {}", endpoint); - let handle = - tokio::spawn(process_request(body, endpoint, auth_header, service)); - handles.push(handle); - } - for handle in handles { - responses.push(handle.await.expect("task panicked")?); - } - } - } - - Ok(responses) - } -} - -struct RecyclableService(RoutesFinalized); - -impl Recyclable for RecyclableService { - fn new() -> Self - where - Self: Sized, - { - unreachable!("won't panic because we use the supplier function") - } - - fn recycle(&mut self) {} -} - -enum Endpoint { - Same(String), - Different(Vec), -} - -fn is_valid_endpoint(endpoint: &str) -> bool { - const ACCEPTED_ENDPOINTS: [&str; 9] = [ - "/protocol.profile.v1.ProfileService/GetProfile", - "/protocol.chat.v1.ChatService/QueryHasPermission", - "/protocol.chat.v1.ChatService/GetUserRoles", - "/protocol.chat.v1.ChatService/GetGuildRoles", - "/protocol.chat.v1.ChatService/GetGuild", - "/protocol.chat.v1.ChatService/GetGuildChannels", - "/protocol.mediaproxy.v1.MediaProxyService/FetchLinkMetadata", - "/protocol.mediaproxy.v1.MediaProxyService/InstantView", - "/protocol.mediaproxy.v1.MediaProxyService/CanInstantView", - ]; - - let endpoint = endpoint.trim_end_matches('/'); - ACCEPTED_ENDPOINTS.contains(&endpoint) -} - -#[derive(Clone)] -pub struct BatchServer { - deps: Arc, - disable_ratelimits: bool, - svc_pool: Arc>, -} - -impl BatchServer { - pub fn new(deps: Arc, svc: Svc) -> Self { - Self { - disable_ratelimits: deps.config.policy.ratelimit.disable, - svc_pool: Arc::new( - PoolBuilder::default() - .with_supplier(move || RecyclableService(svc.make_routes().build())) - .build(), - ), - deps, - } - } - - async fn make_req( - &self, - bodies: Vec, - endpoint: Endpoint, - auth_header: Option, - ) -> ServerResult> { - let mut service = self.svc_pool.get(); - (BatchReq { - bodies, - endpoint, - auth_header, - }) - .process_req(&mut service.0) - .await - } -} - -impl BatchService for BatchServer { - impl_unary_handlers! { - #[rate(10, 4)] - batch, BatchRequest, BatchResponse; - #[rate(10, 4)] - batch_same, BatchSameRequest, BatchSameResponse; - } -} diff --git a/src/impls/chat/channels/create_channel.rs b/src/impls/chat/channels/create_channel.rs index 1276355..3a91398 100644 --- a/src/impls/chat/channels/create_channel.rs +++ b/src/impls/chat/channels/create_channel.rs @@ -39,7 +39,7 @@ pub async fn handler( ) .await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::CreatedChannel(stream_event::ChannelCreated { guild_id, @@ -49,12 +49,7 @@ pub async fn handler( kind, metadata, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + Some(PermCheck::new(guild_id, Some(channel_id), "messages.view")), EventContext::empty(), ); diff --git a/src/impls/chat/channels/delete_channel.rs b/src/impls/chat/channels/delete_channel.rs index ad4e5c4..4247ded 100644 --- a/src/impls/chat/channels/delete_channel.rs +++ b/src/impls/chat/channels/delete_channel.rs @@ -53,7 +53,7 @@ pub async fn handler( .await .map_err(ServerError::DbError)?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::DeletedChannel(stream_event::ChannelDeleted { guild_id, @@ -63,5 +63,5 @@ pub async fn handler( EventContext::empty(), ); - Ok((DeleteChannelResponse {}).into_response()) + Ok(DeleteChannelResponse::new().into_response()) } diff --git a/src/impls/chat/channels/typing.rs b/src/impls/chat/channels/typing.rs index 56493e8..9bc3899 100644 --- a/src/impls/chat/channels/typing.rs +++ b/src/impls/chat/channels/typing.rs @@ -14,27 +14,22 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) .await?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::Typing(stream_event::Typing { user_id, guild_id, channel_id, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + PermCheck::maybe_new(guild_id, channel_id, "messages.view"), EventContext::empty(), ); - Ok((TypingResponse {}).into_response()) + Ok(TypingResponse::new().into_response()) } diff --git a/src/impls/chat/channels/update_all_channel_order.rs b/src/impls/chat/channels/update_all_channel_order.rs index c6bfd2b..56a5028 100644 --- a/src/impls/chat/channels/update_all_channel_order.rs +++ b/src/impls/chat/channels/update_all_channel_order.rs @@ -44,7 +44,7 @@ pub async fn handler( let serialized_ordering = chat_tree.serialize_list_u64_logic(channel_ids.clone()); chat_tree.insert(key, serialized_ordering).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::ChannelsReordered(stream_event::ChannelsReordered { guild_id, @@ -54,5 +54,5 @@ pub async fn handler( EventContext::empty(), ); - Ok((UpdateAllChannelOrderResponse {}).into_response()) + Ok(UpdateAllChannelOrderResponse::new().into_response()) } diff --git a/src/impls/chat/channels/update_channel_information.rs b/src/impls/chat/channels/update_channel_information.rs index 4683d97..6a9ae58 100644 --- a/src/impls/chat/channels/update_channel_information.rs +++ b/src/impls/chat/channels/update_channel_information.rs @@ -50,7 +50,7 @@ pub async fn handler( let buf = rkyv_ser(&chan_info); chat_tree.insert(key, buf).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::EditedChannel(stream_event::ChannelUpdated { guild_id, @@ -58,14 +58,9 @@ pub async fn handler( new_name, new_metadata, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + Some(PermCheck::new(guild_id, Some(channel_id), "messages.view")), EventContext::empty(), ); - Ok((UpdateChannelInformationResponse {}).into_response()) + Ok(UpdateChannelInformationResponse::new().into_response()) } diff --git a/src/impls/chat/channels/update_channel_order.rs b/src/impls/chat/channels/update_channel_order.rs index d244992..07ddcb1 100644 --- a/src/impls/chat/channels/update_channel_order.rs +++ b/src/impls/chat/channels/update_channel_order.rs @@ -32,7 +32,7 @@ pub async fn handler( .update_channel_order_logic(guild_id, channel_id, Some(position.clone())) .await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::EditedChannelPosition(stream_event::ChannelPositionUpdated { guild_id, @@ -44,5 +44,5 @@ pub async fn handler( ); } - Ok((UpdateChannelOrderResponse {}).into_response()) + Ok(UpdateChannelOrderResponse::new().into_response()) } diff --git a/src/impls/chat/guilds/create_direct_message.rs b/src/impls/chat/guilds/create_direct_message.rs deleted file mode 100644 index 9c41d48..0000000 --- a/src/impls/chat/guilds/create_direct_message.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::*; - -pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) -} diff --git a/src/impls/chat/guilds/create_guild.rs b/src/impls/chat/guilds/create_guild.rs index 21a4a61..b4d6657 100644 --- a/src/impls/chat/guilds/create_guild.rs +++ b/src/impls/chat/guilds/create_guild.rs @@ -23,13 +23,7 @@ pub async fn handler( let guild_id = svc .deps .chat_tree - .create_guild_logic( - user_id, - name, - picture, - metadata, - guild_kind::Kind::new_normal(guild_kind::Normal::new()), - ) + .create_guild_logic(user_id, name, picture, metadata) .await?; svc.dispatch_guild_join(guild_id, user_id).await?; diff --git a/src/impls/chat/guilds/create_room.rs b/src/impls/chat/guilds/create_room.rs deleted file mode 100644 index dbd93cf..0000000 --- a/src/impls/chat/guilds/create_room.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::*; - -pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) -} diff --git a/src/impls/chat/guilds/delete_guild.rs b/src/impls/chat/guilds/delete_guild.rs index e4c47ac..635bd8b 100644 --- a/src/impls/chat/guilds/delete_guild.rs +++ b/src/impls/chat/guilds/delete_guild.rs @@ -35,7 +35,7 @@ pub async fn handler( .await .map_err(ServerError::DbError)?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::DeletedGuild(stream_event::GuildDeleted { guild_id }), None, @@ -60,15 +60,15 @@ pub async fn handler( } } } - svc.send_event_through_chan( + svc.broadcast( EventSub::Homeserver, stream_event::Event::GuildRemovedFromList(stream_event::GuildRemovedFromList { guild_id, - homeserver: String::new(), + server_id: None, }), None, EventContext::new(local_ids), ); - Ok((DeleteGuildResponse {}).into_response()) + Ok(DeleteGuildResponse::new().into_response()) } diff --git a/src/impls/chat/guilds/get_guild.rs b/src/impls/chat/guilds/get_guild.rs index 57e61da..1ef33ad 100644 --- a/src/impls/chat/guilds/get_guild.rs +++ b/src/impls/chat/guilds/get_guild.rs @@ -6,14 +6,19 @@ pub async fn handler( ) -> ServerResult> { let user_id = svc.deps.auth(&request).await?; - let GetGuildRequest { guild_id } = request.into_message().await?; + let GetGuildRequest { guild_ids } = request.into_message().await?; let chat_tree = &svc.deps.chat_tree; - chat_tree.check_guild_user(guild_id, user_id).await?; + for guild_id in guild_ids.iter().copied() { + chat_tree.check_guild_user(guild_id, user_id).await?; + } - chat_tree - .get_guild_logic(guild_id) - .await - .map(|g| (GetGuildResponse { guild: Some(g) }).into_response()) + let mut guilds = HashMap::with_capacity(guild_ids.len()); + for guild_id in guild_ids { + let guild = chat_tree.get_guild_logic(guild_id).await?; + guilds.insert(guild_id, guild); + } + + Ok(GetGuildResponse::new(guilds).into_response()) } diff --git a/src/impls/chat/guilds/get_guild_list.rs b/src/impls/chat/guilds/get_guild_list.rs index 393e424..da3cb63 100644 --- a/src/impls/chat/guilds/get_guild_list.rs +++ b/src/impls/chat/guilds/get_guild_list.rs @@ -6,31 +6,7 @@ pub async fn handler( ) -> ServerResult> { let user_id = svc.deps.auth(&request).await?; - let prefix = make_guild_list_key_prefix(user_id); - let guilds = - svc.deps - .chat_tree - .scan_prefix(&prefix) - .await - .try_fold(Vec::new(), |mut all, res| { - let (guild_id_raw, _) = res?; - let (id_raw, host_raw) = guild_id_raw - .split_at(prefix.len()) - .1 - .split_at(size_of::()); - - // Safety: this unwrap can never cause UB since we split at u64 boundary - let guild_id = deser_id(id_raw); - // Safety: we never store non UTF-8 hosts, so this can't cause UB - let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; - - all.push(GuildListEntry { - guild_id, - server_id: host.to_string(), - }); - - ServerResult::Ok(all) - })?; + let guilds = svc.deps.chat_tree.get_user_guilds(user_id).await?; Ok((GetGuildListResponse { guilds }).into_response()) } diff --git a/src/impls/chat/guilds/join_guild.rs b/src/impls/chat/guilds/join_guild.rs index 0c4812c..574469b 100644 --- a/src/impls/chat/guilds/join_guild.rs +++ b/src/impls/chat/guilds/join_guild.rs @@ -14,15 +14,15 @@ pub async fn handler( let (guild_id, mut invite) = if let Some(raw) = chat_tree.get(&key).await? { db::deser_invite_entry(raw) } else { - return Err(ServerError::NoSuchInvite(invite_id.into()).into()); + bail!(ServerError::NoSuchInvite(invite_id.into())); }; if chat_tree.is_user_banned_in_guild(guild_id, user_id).await? { - return Err(ServerError::UserBanned.into()); + bail!(ServerError::UserBanned); } chat_tree - .is_user_in_guild(guild_id, user_id) + .check_user_in_guild(guild_id, user_id) .await .ok() .map_or(Ok(()), |_| Err(ServerError::UserAlreadyInGuild))?; @@ -44,7 +44,7 @@ pub async fn handler( chat_tree.delete_invite_logic(invite_id).await?; } - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::JoinedMember(stream_event::MemberJoined { guild_id, diff --git a/src/impls/chat/guilds/leave_guild.rs b/src/impls/chat/guilds/leave_guild.rs index 71eef6c..4258b9a 100644 --- a/src/impls/chat/guilds/leave_guild.rs +++ b/src/impls/chat/guilds/leave_guild.rs @@ -22,7 +22,7 @@ pub async fn handler( .await .map_err(ServerError::DbError)?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::LeftMember(stream_event::MemberLeft { guild_id, @@ -35,5 +35,5 @@ pub async fn handler( svc.dispatch_guild_leave(guild_id, user_id).await?; - Ok((LeaveGuildResponse {}).into_response()) + Ok(LeaveGuildResponse::new().into_response()) } diff --git a/src/impls/chat/guilds/mod.rs b/src/impls/chat/guilds/mod.rs index d9c1094..11232c5 100644 --- a/src/impls/chat/guilds/mod.rs +++ b/src/impls/chat/guilds/mod.rs @@ -1,8 +1,6 @@ use super::*; -pub mod create_direct_message; pub mod create_guild; -pub mod create_room; pub mod delete_guild; pub mod get_guild; pub mod get_guild_list; @@ -11,4 +9,3 @@ pub mod join_guild; pub mod leave_guild; pub mod preview_guild; pub mod update_guild_information; -pub mod upgrade_room_to_guild; diff --git a/src/impls/chat/guilds/update_guild_information.rs b/src/impls/chat/guilds/update_guild_information.rs index 550a0ea..7fb63a2 100644 --- a/src/impls/chat/guilds/update_guild_information.rs +++ b/src/impls/chat/guilds/update_guild_information.rs @@ -44,7 +44,7 @@ pub async fn handler( chat_tree.put_guild_logic(guild_id, guild_info).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::EditedGuild(stream_event::GuildUpdated { guild_id, @@ -56,5 +56,5 @@ pub async fn handler( EventContext::empty(), ); - Ok((UpdateGuildInformationResponse {}).into_response()) + Ok(UpdateGuildInformationResponse::new().into_response()) } diff --git a/src/impls/chat/guilds/upgrade_room_to_guild.rs b/src/impls/chat/guilds/upgrade_room_to_guild.rs deleted file mode 100644 index 09f43f1..0000000 --- a/src/impls/chat/guilds/upgrade_room_to_guild.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::*; - -pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) -} diff --git a/src/impls/chat/invites/delete_invite.rs b/src/impls/chat/invites/delete_invite.rs index 4a42a24..7625961 100644 --- a/src/impls/chat/invites/delete_invite.rs +++ b/src/impls/chat/invites/delete_invite.rs @@ -20,5 +20,5 @@ pub async fn handler( chat_tree.delete_invite_logic(invite_id).await?; - Ok((DeleteInviteResponse {}).into_response()) + Ok(DeleteInviteResponse::new().into_response()) } diff --git a/src/impls/chat/invites/get_pending_invites.rs b/src/impls/chat/invites/get_pending_invites.rs index b1ad171..ee4d70c 100644 --- a/src/impls/chat/invites/get_pending_invites.rs +++ b/src/impls/chat/invites/get_pending_invites.rs @@ -1,8 +1,17 @@ use super::*; pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let pending = svc + .deps + .chat_tree + .get(make_user_pending_invites_key(user_id)) + .await? + .map_or_else(UserPendingInvites::default, db::deser_pending_invites); + + Ok(GetPendingInvitesResponse::new(pending.invites).into_response()) } diff --git a/src/impls/chat/invites/ignore_pending_invite.rs b/src/impls/chat/invites/ignore_pending_invite.rs index 73b3251..3447d82 100644 --- a/src/impls/chat/invites/ignore_pending_invite.rs +++ b/src/impls/chat/invites/ignore_pending_invite.rs @@ -1,8 +1,33 @@ use super::*; pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let IgnorePendingInviteRequest { + server_id, + location, + } = request.into_message().await?; + + let location = location.ok_or(( + "h.location-expected", + "location not specified while it was expected", + ))?; + let location = match location { + ignore_pending_invite_request::Location::ChannelId(channel_id) => { + pending_invite::Location::ChannelId(channel_id) + } + ignore_pending_invite_request::Location::GuildInviteId(invite_id) => { + pending_invite::Location::GuildInviteId(invite_id) + } + }; + + svc.deps + .chat_tree + .remove_user_pending_invite(user_id, server_id.as_deref(), &location) + .await?; + + Ok(IgnorePendingInviteResponse::new().into_response()) } diff --git a/src/impls/chat/invites/reject_pending_invite.rs b/src/impls/chat/invites/reject_pending_invite.rs index 437a7d5..0826459 100644 --- a/src/impls/chat/invites/reject_pending_invite.rs +++ b/src/impls/chat/invites/reject_pending_invite.rs @@ -1,8 +1,45 @@ use super::*; pub async fn handler( - _svc: &ChatServer, - _request: Request, -) -> ServerResult> { - Err(ServerError::NotImplemented.into()) + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let RejectPendingInviteRequest { + server_id, + location, + } = request.into_message().await?; + + let location = location.ok_or(( + "h.location-expected", + "location not specified while it was expected", + ))?; + let location = match location { + reject_pending_invite_request::Location::ChannelId(channel_id) => { + pending_invite::Location::ChannelId(channel_id) + } + reject_pending_invite_request::Location::GuildInviteId(invite_id) => { + pending_invite::Location::GuildInviteId(invite_id) + } + }; + + let invite = svc + .deps + .chat_tree + .remove_user_pending_invite(user_id, server_id.as_deref(), &location) + .await?; + + let location = match location { + pending_invite::Location::ChannelId(channel_id) => { + user_rejected_invite::Location::ChannelId(channel_id) + } + pending_invite::Location::GuildInviteId(invite_id) => { + user_rejected_invite::Location::GuildInviteId(invite_id) + } + }; + svc.dispatch_user_invite_rejected(invite.inviter_id, user_id, location) + .await?; + + Ok(RejectPendingInviteResponse::new().into_response()) } diff --git a/src/impls/chat/messages/add_reaction.rs b/src/impls/chat/messages/add_reaction.rs index 411994f..c6b0812 100644 --- a/src/impls/chat/messages/add_reaction.rs +++ b/src/impls/chat/messages/add_reaction.rs @@ -10,27 +10,54 @@ pub async fn handler( guild_id, channel_id, message_id, - emote, + reaction_data, + reaction_kind, + reaction_name, } = request.into_message().await?; + let reaction_kind = ReactionKind::from_i32(reaction_kind).unwrap_or_default(); - if let Some(emote) = emote { - let chat_tree = &svc.deps.chat_tree; + let chat_tree = &svc.deps.chat_tree; - chat_tree - .check_perms( - guild_id, - Some(channel_id), - user_id, - all_permissions::MESSAGES_REACTIONS_ADD, - false, - ) - .await?; + chat_tree + .check_channel_user(guild_id, user_id, channel_id) + .await?; - let reaction = chat_tree - .update_reaction(user_id, guild_id, channel_id, message_id, emote, true) - .await?; - svc.send_reaction_event(guild_id, channel_id, message_id, reaction); + chat_tree + .check_perms( + guild_id, + Some(channel_id), + user_id, + all_permissions::MESSAGES_REACTIONS_ADD, + false, + ) + .await?; + + let did_add = chat_tree + .add_reaction( + user_id, + guild_id, + channel_id, + message_id, + reaction_data.clone(), + reaction_name.clone(), + reaction_kind, + ) + .await?; + if did_add { + svc.send_new_reaction_event( + guild_id, + channel_id, + message_id, + Reaction { + count: 1, + data: reaction_data, + kind: reaction_kind.into(), + name: reaction_name, + }, + ); + } else { + svc.send_added_reaction_event(guild_id, channel_id, message_id, reaction_data); } - Ok((AddReactionResponse {}).into_response()) + Ok(AddReactionResponse::new().into_response()) } diff --git a/src/impls/chat/messages/delete_message.rs b/src/impls/chat/messages/delete_message.rs index 764d0a0..39ff960 100644 --- a/src/impls/chat/messages/delete_message.rs +++ b/src/impls/chat/messages/delete_message.rs @@ -15,7 +15,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; if chat_tree .get_message_logic(guild_id, channel_id, message_id) @@ -41,21 +41,16 @@ pub async fn handler( .await .map_err(ServerError::DbError)?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::DeletedMessage(stream_event::MessageDeleted { guild_id, channel_id, message_id, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + PermCheck::maybe_new(guild_id, channel_id, "messages.view"), EventContext::empty(), ); - Ok((DeleteMessageResponse {}).into_response()) + Ok(DeleteMessageResponse::new().into_response()) } diff --git a/src/impls/chat/messages/get_channel_messages.rs b/src/impls/chat/messages/get_channel_messages.rs index 7adc637..3e0091f 100644 --- a/src/impls/chat/messages/get_channel_messages.rs +++ b/src/impls/chat/messages/get_channel_messages.rs @@ -17,7 +17,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree .check_perms(guild_id, Some(channel_id), user_id, "messages.view", false) diff --git a/src/impls/chat/messages/get_message.rs b/src/impls/chat/messages/get_message.rs index 3a0ff65..dfab468 100644 --- a/src/impls/chat/messages/get_message.rs +++ b/src/impls/chat/messages/get_message.rs @@ -17,7 +17,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree .check_perms(guild_id, Some(channel_id), user_id, "messages.view", false) diff --git a/src/impls/chat/messages/get_pinned_messages.rs b/src/impls/chat/messages/get_pinned_messages.rs index da18682..9cd7293 100644 --- a/src/impls/chat/messages/get_pinned_messages.rs +++ b/src/impls/chat/messages/get_pinned_messages.rs @@ -14,7 +14,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree diff --git a/src/impls/chat/messages/mod.rs b/src/impls/chat/messages/mod.rs index 11766b4..38f3e0b 100644 --- a/src/impls/chat/messages/mod.rs +++ b/src/impls/chat/messages/mod.rs @@ -9,4 +9,4 @@ pub mod pin_message; pub mod remove_reaction; pub mod send_message; pub mod unpin_message; -pub mod update_message_text; +pub mod update_message_content; diff --git a/src/impls/chat/messages/pin_message.rs b/src/impls/chat/messages/pin_message.rs index 8e3fa37..a312e8c 100644 --- a/src/impls/chat/messages/pin_message.rs +++ b/src/impls/chat/messages/pin_message.rs @@ -15,7 +15,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree @@ -29,23 +29,18 @@ pub async fn handler( .await?; let key = make_pinned_msgs_key(guild_id, channel_id); - let mut pinned_msgs_raw = chat_tree.get(key).await?.map_or_else(Vec::new, EVec::into); + let mut pinned_msgs_raw = chat_tree.get(&key).await?.map_or_else(Vec::new, EVec::into); pinned_msgs_raw.extend_from_slice(&message_id.to_be_bytes()); chat_tree.insert(key, pinned_msgs_raw).await?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::MessagePinned(stream_event::MessagePinned { guild_id, channel_id, message_id, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - all_permissions::MESSAGES_VIEW, - false, - )), + PermCheck::maybe_new(guild_id, channel_id, all_permissions::MESSAGES_VIEW), EventContext::empty(), ); diff --git a/src/impls/chat/messages/remove_reaction.rs b/src/impls/chat/messages/remove_reaction.rs index 8b1053b..d2c373e 100644 --- a/src/impls/chat/messages/remove_reaction.rs +++ b/src/impls/chat/messages/remove_reaction.rs @@ -10,29 +10,29 @@ pub async fn handler( guild_id, channel_id, message_id, - emote, + reaction_data, } = request.into_message().await?; - if let Some(emote) = emote { - let chat_tree = &svc.deps.chat_tree; + let chat_tree = &svc.deps.chat_tree; - chat_tree - .check_perms( - guild_id, - Some(channel_id), - user_id, - all_permissions::MESSAGES_REACTIONS_REMOVE, - false, - ) - .await?; + chat_tree + .check_channel_user(guild_id, user_id, channel_id) + .await?; - let reaction = chat_tree - .update_reaction(user_id, guild_id, channel_id, message_id, emote, false) - .await?; - if reaction.is_some() { - svc.send_reaction_event(guild_id, channel_id, message_id, reaction); - } - } + chat_tree + .check_perms( + guild_id, + Some(channel_id), + user_id, + all_permissions::MESSAGES_REACTIONS_REMOVE, + false, + ) + .await?; - Ok((RemoveReactionResponse {}).into_response()) + chat_tree + .remove_reaction(user_id, guild_id, channel_id, message_id, &reaction_data) + .await?; + svc.send_removed_reaction_event(guild_id, channel_id, message_id, reaction_data); + + Ok(RemoveReactionResponse::new().into_response()) } diff --git a/src/impls/chat/messages/send_message.rs b/src/impls/chat/messages/send_message.rs index 9e11182..c6e3828 100644 --- a/src/impls/chat/messages/send_message.rs +++ b/src/impls/chat/messages/send_message.rs @@ -1,3 +1,5 @@ +use hrpc::exports::futures_util::FutureExt; + use crate::impls::admin_action; use super::*; @@ -16,46 +18,64 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) .await?; - chat_tree.process_message_overrides(request.overrides.as_ref())?; + // check if the in reply to message actually exists + if let Some(in_reply_to) = request.in_reply_to { + let has_msg = chat_tree + .contains_key(make_msg_key(guild_id, channel_id, in_reply_to)) + .await?; + if has_msg.not() { + bail!(ServerError::NoSuchMessage { + guild_id, + channel_id, + message_id: in_reply_to + }); + } + } + + // check overrides + chat_tree.check_message_overrides(request.overrides.as_ref())?; + + // process content (attachments etc) let content = chat_tree - .process_message_content( - request.content.take(), - svc.deps.config.media.media_root.as_path(), - &svc.deps.config.host, - ) + .process_message_content(svc.deps.as_ref(), request.content.take()) .await?; - request.content = Some(content); - let (message_id, message) = chat_tree.send_message_logic(user_id, request).await?; - let is_cmd_channel = chat_tree - .admin_guild_keys - .get() - .map_or(false, |keys| keys.check_if_cmd(guild_id, channel_id)); + let admin_action = guild_id.and_then(|guild_id| { + chat_tree + .admin_guild_keys + .get() + .map_or(false, |keys| keys.check_if_cmd(guild_id, channel_id)) + .then(|| content.text.parse::().ok()) + .flatten() + }); - let action_content = if is_cmd_channel { - if let Some(content::Content::TextMessage(content::TextContent { - content: Some(FormattedText { text, .. }), - })) = message.content.as_ref().and_then(|c| c.content.as_ref()) - { - let msg = admin_action::run_str(svc.deps.as_ref(), text) - .await - .unwrap_or_else(|err| format!("error: {}", err)); - Some(msg) - } else { - None - } - } else { - None - }; + let (message_id, message) = chat_tree + .send_message_logic( + guild_id, + channel_id, + user_id, + content, + request.overrides, + request.in_reply_to, + request.metadata, + request.actions, + ) + .await?; + + let action_content = opt_fut(admin_action.map(|action| { + admin_action::run(svc.deps.as_ref(), action) + .map(|err| err.unwrap_or_else(|err| format!("error: {err}"))) + })) + .await; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::SentMessage(stream_event::MessageSent { echo_id, guild_id, @@ -63,24 +83,17 @@ pub async fn handler( message_id, message: Some(message), }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + PermCheck::maybe_new(guild_id, channel_id, "messages.view"), EventContext::empty(), ); if let Some(msg) = action_content { - let content = content::Content::TextMessage(content::TextContent { - content: Some(FormattedText::new(msg, Vec::new())), - }); + let content = Content::default().with_text(msg); let (message_id, message) = chat_tree .send_with_system(guild_id, channel_id, content) .await?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::SentMessage(stream_event::MessageSent { echo_id, guild_id, @@ -88,12 +101,7 @@ pub async fn handler( message_id, message: Some(message), }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + PermCheck::maybe_new(guild_id, channel_id, "messages.view"), EventContext::empty(), ); } diff --git a/src/impls/chat/messages/unpin_message.rs b/src/impls/chat/messages/unpin_message.rs index 3638d35..6675ccf 100644 --- a/src/impls/chat/messages/unpin_message.rs +++ b/src/impls/chat/messages/unpin_message.rs @@ -15,7 +15,7 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree @@ -39,19 +39,14 @@ pub async fn handler( .insert(make_pinned_msgs_key(guild_id, channel_id), pinned_msgs_raw) .await?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::MessageUnpinned(stream_event::MessageUnpinned { guild_id, channel_id, message_id, }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - all_permissions::MESSAGES_VIEW, - false, - )), + PermCheck::maybe_new(guild_id, channel_id, all_permissions::MESSAGES_VIEW), EventContext::empty(), ); diff --git a/src/impls/chat/messages/update_message_text.rs b/src/impls/chat/messages/update_message_content.rs similarity index 57% rename from src/impls/chat/messages/update_message_text.rs rename to src/impls/chat/messages/update_message_content.rs index 84ac07d..993fd9a 100644 --- a/src/impls/chat/messages/update_message_text.rs +++ b/src/impls/chat/messages/update_message_content.rs @@ -4,13 +4,13 @@ use super::*; pub async fn handler( svc: &ChatServer, - request: Request, -) -> ServerResult> { + request: Request, +) -> ServerResult> { let user_id = svc.deps.auth(&request).await?; let request = request.into_message().await?; - let UpdateMessageTextRequest { + let UpdateMessageContentRequest { guild_id, channel_id, message_id, @@ -20,18 +20,18 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree - .check_guild_user_channel(guild_id, user_id, channel_id) + .check_channel_user(guild_id, user_id, channel_id) .await?; chat_tree .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) .await?; - if new_content.as_ref().map_or(true, |f| f.text.is_empty()) { - return Err(ServerError::MessageContentCantBeEmpty.into()); - } + let new_content = chat_tree + .process_message_content(&svc.deps, new_content) + .await?; let key = make_msg_key(guild_id, channel_id, message_id); - let Some(message_raw) = chat_tree.get(key).await? else { + let Some(message_raw) = chat_tree.get(&key).await? else { bail!(ServerError::NoSuchMessage { guild_id, channel_id, message_id }); }; let message_archived = rkyv_arch::(&message_raw); @@ -47,39 +47,25 @@ pub async fn handler( .deserialize(&mut SharedDeserializeMap::default()) .unwrap(); - let msg_content = if let Some(content) = &mut message.content { - content - } else { - message.content = Some(Content::default()); - message.content.as_mut().unwrap() - }; - msg_content.content = Some(content::Content::TextMessage(content::TextContent { - content: new_content.clone(), - })); - let edited_at = get_time_secs(); + message.content = Some(new_content.clone()); message.edited_at = Some(edited_at); let buf = rkyv_ser(&message); chat_tree.insert(key, buf).await?; - svc.send_event_through_chan( - EventSub::Guild(guild_id), + svc.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), stream_event::Event::EditedMessage(stream_event::MessageUpdated { guild_id, channel_id, message_id, edited_at, - new_content, + new_content: Some(new_content), }), - Some(PermCheck::new( - guild_id, - Some(channel_id), - "messages.view", - false, - )), + PermCheck::maybe_new(guild_id, channel_id, "messages.view"), EventContext::empty(), ); - Ok((UpdateMessageTextResponse {}).into_response()) + Ok(UpdateMessageContentResponse::new().into_response()) } diff --git a/src/impls/chat/mod.rs b/src/impls/chat/mod.rs index 21b5c84..4ec758c 100644 --- a/src/impls/chat/mod.rs +++ b/src/impls/chat/mod.rs @@ -1,27 +1,32 @@ use std::{ - collections::HashSet, io::BufReader, iter, lazy::SyncOnceCell, mem::size_of, ops::Not, - path::Path, str::FromStr, + collections::HashSet, io::BufReader, lazy::SyncOnceCell, mem::size_of, ops::Not, path::Path, }; -use crate::api::{ - chat::{ - get_channel_messages_request::Direction, overrides::Reason, permission::has_permission, - stream_event, FormattedText, Message as HarmonyMessage, *, - }, - emote::Emote, - exports::hrpc::{server::socket::Socket, Request}, - harmonytypes::{item_position, Empty, ItemPosition, Metadata}, - rest::FileId, - sync::{ - event::{ - Kind as DispatchKind, UserAddedToGuild as SyncUserAddedToGuild, - UserRemovedFromGuild as SyncUserRemovedFromGuild, +use crate::{ + api::{ + chat::{ + attachment::ImageInfo, get_channel_messages_request::Direction, overrides::Reason, + permission::has_permission, + send_message_request::attachment::ImageInfo as SendImageInfo, stream_event, + Message as HarmonyMessage, *, + }, + exports::hrpc::{server::socket::Socket, Request}, + harmonytypes::{item_position, Empty, ItemPosition, Metadata}, + sync::{ + event::{ + user_invited, user_rejected_invite, Kind as DispatchKind, + UserAddedToChannel as SyncUserAddedToChannel, + UserAddedToGuild as SyncUserAddedToGuild, UserInvited as SyncUserInvited, + UserRejectedInvite as SyncUserRejectedInvite, + UserRemovedFromChannel as SyncUserRemovedFromChannel, + UserRemovedFromGuild as SyncUserRemovedFromGuild, + }, + Event as DispatchEvent, }, - Event as DispatchEvent, }, - Hmc, + db::deser_invite_entry, }; -use image::GenericImageView; +use image::{GenericImageView, ImageFormat}; use rand::{Rng, SeedableRng}; use scherzo_derive::*; use smol_str::SmolStr; @@ -33,12 +38,7 @@ use triomphe::Arc; use crate::{ db::{self, chat::*, rkyv_ser, Batch, Db, DbResult}, - impls::{ - get_time_millisecs, - prelude::*, - rest::download::{calculate_range, get_file_full, get_file_handle, is_id_jpeg, read_bufs}, - sync::EventDispatch, - }, + impls::{get_time_secs, prelude::*, sync::EventDispatch}, }; use channels::*; @@ -47,6 +47,9 @@ use invites::*; use messages::*; use moderation::*; use permissions::*; +use private_channels::*; + +use super::media::FileHandle; pub mod channels; pub mod guilds; @@ -54,6 +57,7 @@ pub mod invites; pub mod messages; pub mod moderation; pub mod permissions; +pub mod private_channels; pub mod stream_events; pub mod trigger_action; @@ -61,6 +65,7 @@ pub const DEFAULT_ROLE_ID: u64 = 0; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EventSub { + PrivateChannel(u64), Guild(u64), Homeserver, Actions, @@ -75,19 +80,23 @@ pub struct PermCheck<'a> { } impl<'a> PermCheck<'a> { - pub const fn new( - guild_id: u64, - channel_id: Option, - check_for: &'a str, - must_be_guild_owner: bool, - ) -> Self { + pub const fn new(guild_id: u64, channel_id: Option, check_for: &'a str) -> Self { Self { guild_id, channel_id, check_for, - must_be_guild_owner, + must_be_guild_owner: false, } } + + pub fn maybe_new(guild_id: Option, channel_id: u64, check_for: &'a str) -> Option { + guild_id.map(|guild_id| Self::new(guild_id, Some(channel_id), check_for)) + } + + pub fn must_be_guild_owner(mut self) -> Self { + self.must_be_guild_owner = true; + self + } } #[derive(Debug)] @@ -168,18 +177,17 @@ impl ChatServer { let mut subs = HashSet::with_hasher(ahash::RandomState::new()); // add initial subs - // TODO: optimize local guild fetching - let user_guilds = chat_tree.get_user_guilds(user_id).await?; - let initial_subs = user_guilds - .into_iter() - .filter(|g| g.server_id.is_empty()) - .map(|g| EventSub::Guild(g.guild_id)); - let initial_subs = initial_subs - .chain(iter::once(EventSub::Actions)) - .chain(iter::once(EventSub::Homeserver)); - for sub in initial_subs { - subs.insert(sub); - } + let user_guilds = chat_tree.get_user_local_guilds(user_id).await?; + let user_pcs = chat_tree.get_user_local_private_channels(user_id).await?; + let initial_subs = user_guilds.into_iter().map(|e| EventSub::Guild(e.guild_id)); + let initial_subs = initial_subs.chain( + user_pcs + .into_iter() + .map(|e| EventSub::PrivateChannel(e.channel_id)), + ); + let initial_subs = + initial_subs.chain([EventSub::Actions, EventSub::Homeserver].into_iter()); + subs.extend(initial_subs); // keep track of failed writes and reads to decide if closing the socket is worth it let mut failed_writes: u8 = 0; @@ -215,6 +223,15 @@ impl ChatServer { tracing::debug!("got new stream events request"); let sub = match req { + Request::SubscribeToPrivateChannel(SubscribeToPrivateChannel { channel_id }) => { + match chat_tree.check_private_channel_user(channel_id, user_id).await { + Ok(_) => EventSub::PrivateChannel(channel_id), + Err(err) => { + tracing::error!("{}", err); + continue; + } + } + }, Request::SubscribeToGuild(SubscribeToGuild { guild_id }) => { match chat_tree.check_guild_user(guild_id, user_id).await { Ok(_) => EventSub::Guild(guild_id), @@ -242,9 +259,16 @@ impl ChatServer { // handle automatic sub handling BEFORE all the other logic because otherwise // `subs.contains()` will just return if manual_sub_handling.not() { + // TODO: i think we need to check whether the server_id is empty here before inserting? match &broadcast.event { - Event::Chat(stream_event::Event::GuildAddedToList(guild)) => subs.insert(EventSub::Guild(guild.guild_id)), - Event::Chat(stream_event::Event::GuildRemovedFromList(guild)) => subs.remove(&EventSub::Guild(guild.guild_id)), + Event::Chat(stream_event::Event::GuildAddedToList(guild)) + => subs.insert(EventSub::Guild(guild.guild_id)), + Event::Chat(stream_event::Event::GuildRemovedFromList(guild)) + => subs.remove(&EventSub::Guild(guild.guild_id)), + Event::Chat(stream_event::Event::PrivateChannelAddedToList(channel)) + => subs.insert(EventSub::PrivateChannel(channel.channel_id)), + Event::Chat(stream_event::Event::PrivateChannelRemovedFromList(channel)) + => subs.remove(&EventSub::PrivateChannel(channel.channel_id)), _ => false, }; } @@ -316,26 +340,14 @@ impl ChatServer { } #[inline(always)] - fn send_event_through_chan( + pub fn broadcast( &self, sub: EventSub, event: stream_event::Event, perm_check: Option>, context: EventContext, ) { - let broadcast = EventBroadcast { - sub, - event: Event::Chat(event), - perm_check, - context, - }; - - tracing::debug!( - "broadcasting events to {} receivers", - self.deps.chat_event_sender.receiver_count() - ); - - drop(self.deps.chat_event_sender.send(Arc::new(broadcast))); + self.deps.broadcast_chat(sub, event, perm_check, context) } #[inline(always)] @@ -361,11 +373,11 @@ impl ChatServer { .chat_tree .remove_guild_from_guild_list(user_id, guild_id, "") .await?; - self.send_event_through_chan( + self.broadcast( EventSub::Homeserver, stream_event::Event::GuildRemovedFromList(stream_event::GuildRemovedFromList { guild_id, - homeserver: String::new(), + server_id: None, }), None, EventContext::new(vec![user_id]), @@ -389,11 +401,11 @@ impl ChatServer { .chat_tree .add_guild_to_guild_list(user_id, guild_id, "") .await?; - self.send_event_through_chan( + self.broadcast( EventSub::Homeserver, stream_event::Event::GuildAddedToList(stream_event::GuildAddedToList { guild_id, - homeserver: String::new(), + server_id: None, }), None, EventContext::new(vec![user_id]), @@ -403,27 +415,236 @@ impl ChatServer { Ok(()) } - fn send_reaction_event( + async fn dispatch_private_channel_leave( &self, - guild_id: u64, + channel_id: u64, + user_id: u64, + ) -> ServerResult<()> { + match self.deps.profile_tree.local_to_foreign_id(user_id).await? { + Some((foreign_id, target)) => self.dispatch_event( + target, + DispatchKind::UserRemovedFromChannel(SyncUserRemovedFromChannel { + user_id: foreign_id, + channel_id, + }), + ), + None => { + self.deps + .chat_tree + .remove_pc_from_pc_list(user_id, channel_id, "") + .await?; + self.broadcast( + EventSub::Homeserver, + stream_event::Event::PrivateChannelRemovedFromList( + stream_event::PrivateChannelRemovedFromList { + channel_id, + server_id: None, + }, + ), + None, + EventContext::new(vec![user_id]), + ); + } + } + Ok(()) + } + + async fn dispatch_private_channel_join( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult<()> { + match self.deps.profile_tree.local_to_foreign_id(user_id).await? { + Some((foreign_id, target)) => self.dispatch_event( + target, + DispatchKind::UserAddedToChannel(SyncUserAddedToChannel { + user_id: foreign_id, + channel_id, + }), + ), + None => { + self.deps + .chat_tree + .add_pc_to_pc_list(user_id, channel_id, "") + .await?; + self.broadcast( + EventSub::Homeserver, + stream_event::Event::PrivateChannelAddedToList( + stream_event::PrivateChannelAddedToList { + channel_id, + server_id: None, + }, + ), + None, + EventContext::new(vec![user_id]), + ); + } + } + Ok(()) + } + + async fn dispatch_user_invite_rejected( + &self, + inviter_id: u64, + invitee_id: u64, + location: user_rejected_invite::Location, + ) -> ServerResult<()> { + match self + .deps + .profile_tree + .local_to_foreign_id(inviter_id) + .await? + { + Some((foreign_id, target)) => self.dispatch_event( + target, + DispatchKind::UserRejectedInvite(SyncUserRejectedInvite { + user_id: invitee_id, + inviter_id: foreign_id, + location: Some(location), + }), + ), + None => { + let location = match location { + user_rejected_invite::Location::ChannelId(channel_id) => { + stream_event::invite_rejected::Location::ChannelId(channel_id) + } + user_rejected_invite::Location::GuildInviteId(invite_id) => { + stream_event::invite_rejected::Location::GuildInviteId(invite_id) + } + }; + self.broadcast( + EventSub::Homeserver, + stream_event::Event::InviteRejected(stream_event::InviteRejected { + invitee_id, + location: Some(location), + server_id: None, + }), + None, + EventContext::new(vec![inviter_id]), + ); + } + } + Ok(()) + } + + async fn dispatch_user_invite_received( + &self, + inviter_id: u64, + invitee_id: u64, + location: pending_invite::Location, + ) -> ServerResult<()> { + match self + .deps + .profile_tree + .local_to_foreign_id(invitee_id) + .await? + { + Some((foreign_id, target)) => self.dispatch_event( + target, + DispatchKind::UserInvited(SyncUserInvited { + user_id: foreign_id, + inviter_id, + location: Some(match location { + pending_invite::Location::ChannelId(channel_id) => { + user_invited::Location::ChannelId(channel_id) + } + pending_invite::Location::GuildInviteId(invite_id) => { + user_invited::Location::GuildInviteId(invite_id) + } + }), + }), + ), + None => { + self.deps + .chat_tree + .add_user_pending_invite( + invitee_id, + PendingInvite { + inviter_id, + location: Some(location.clone()), + server_id: None, + }, + ) + .await?; + let location = match location { + pending_invite::Location::ChannelId(channel_id) => { + stream_event::invite_received::Location::ChannelId(channel_id) + } + pending_invite::Location::GuildInviteId(invite_id) => { + stream_event::invite_received::Location::GuildInviteId(invite_id) + } + }; + self.broadcast( + EventSub::Homeserver, + stream_event::Event::InviteReceived(stream_event::InviteReceived { + inviter_id, + location: Some(location), + server_id: None, + }), + None, + EventContext::new(vec![invitee_id]), + ); + } + } + Ok(()) + } + + fn send_new_reaction_event( + &self, + guild_id: Option, channel_id: u64, message_id: u64, - reaction: Option, + reaction: Reaction, ) { - self.send_event_through_chan( - EventSub::Guild(guild_id), - stream_event::Event::ReactionUpdated(stream_event::ReactionUpdated { + self.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), + stream_event::Event::NewReactionAdded(stream_event::NewReactionAdded { guild_id, channel_id, message_id, - reaction, + reaction: Some(reaction), }), - Some(PermCheck { + PermCheck::maybe_new(guild_id, channel_id, all_permissions::MESSAGES_VIEW), + EventContext::empty(), + ); + } + + fn send_added_reaction_event( + &self, + guild_id: Option, + channel_id: u64, + message_id: u64, + data: String, + ) { + self.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), + stream_event::Event::ReactionAdded(stream_event::ReactionAdded { guild_id, - channel_id: Some(channel_id), - check_for: all_permissions::MESSAGES_VIEW, - must_be_guild_owner: false, + channel_id, + message_id, + reaction_data: data, }), + PermCheck::maybe_new(guild_id, channel_id, all_permissions::MESSAGES_VIEW), + EventContext::empty(), + ); + } + + fn send_removed_reaction_event( + &self, + guild_id: Option, + channel_id: u64, + message_id: u64, + data: String, + ) { + self.broadcast( + guild_id.map_or(EventSub::PrivateChannel(channel_id), EventSub::Guild), + stream_event::Event::ReactionRemoved(stream_event::ReactionRemoved { + guild_id, + channel_id, + message_id, + reaction_data: data, + }), + PermCheck::maybe_new(guild_id, channel_id, all_permissions::MESSAGES_VIEW), EventContext::empty(), ); } @@ -431,11 +652,11 @@ impl ChatServer { impl chat_service_server::ChatService for ChatServer { impl_unary_handlers! { - #[rate(1, 5)] + #[rate(1, 20)] create_guild, CreateGuildRequest, CreateGuildResponse; - #[rate(3, 5)] + #[rate(3, 10)] create_invite, CreateInviteRequest, CreateInviteResponse; - #[rate(4, 5)] + #[rate(4, 8)] create_channel, CreateChannelRequest, CreateChannelResponse; #[rate(5, 5)] get_guild_list, GetGuildListRequest, GetGuildListResponse; @@ -460,7 +681,7 @@ impl chat_service_server::ChatService for ChatServer { #[rate(1, 5)] update_all_channel_order, UpdateAllChannelOrderRequest, UpdateAllChannelOrderResponse; #[rate(5, 5)] - update_message_text, UpdateMessageTextRequest, UpdateMessageTextResponse; + update_message_content, UpdateMessageContentRequest, UpdateMessageContentResponse; #[rate(1, 15)] delete_guild, DeleteGuildRequest, DeleteGuildResponse; #[rate(4, 5)] @@ -469,7 +690,7 @@ impl chat_service_server::ChatService for ChatServer { delete_channel, DeleteChannelRequest, DeleteChannelResponse; #[rate(7, 5)] delete_message, DeleteMessageRequest, DeleteMessageResponse; - #[rate(3, 5)] + #[rate(3, 15)] join_guild, JoinGuildRequest, JoinGuildResponse; #[rate(3, 5)] leave_guild, LeaveGuildRequest, LeaveGuildResponse; @@ -478,7 +699,7 @@ impl chat_service_server::ChatService for ChatServer { #[rate(15, 8)] send_message, SendMessageRequest, SendMessageResponse; #[rate(5, 7)] - query_has_permission, QueryHasPermissionRequest, QueryHasPermissionResponse; + has_permission, HasPermissionRequest, HasPermissionResponse; #[rate(5, 7)] set_permissions, SetPermissionsRequest, SetPermissionsResponse; #[rate(7, 5)] @@ -509,8 +730,11 @@ impl chat_service_server::ChatService for ChatServer { kick_user, KickUserRequest, KickUserResponse; #[rate(4, 5)] unban_user, UnbanUserRequest, UnbanUserResponse; + #[rate(4, 5)] get_pinned_messages, GetPinnedMessagesRequest, GetPinnedMessagesResponse; + #[rate(4, 10)] pin_message, PinMessageRequest, PinMessageResponse; + #[rate(4, 10)] unpin_message, UnpinMessageRequest, UnpinMessageResponse; #[rate(5, 7)] add_reaction, AddReactionRequest, AddReactionResponse; @@ -520,12 +744,27 @@ impl chat_service_server::ChatService for ChatServer { grant_ownership, GrantOwnershipRequest, GrantOwnershipResponse; #[rate(2, 60)] give_up_ownership, GiveUpOwnershipRequest, GiveUpOwnershipResponse; - create_room, CreateRoomRequest, CreateRoomResponse; - create_direct_message, CreateDirectMessageRequest, CreateDirectMessageResponse; - upgrade_room_to_guild, UpgradeRoomToGuildRequest, UpgradeRoomToGuildResponse; + #[rate(5, 5)] + get_private_channel, GetPrivateChannelRequest, GetPrivateChannelResponse; + #[rate(1, 20)] + create_private_channel, CreatePrivateChannelRequest, CreatePrivateChannelResponse; + #[rate(3, 8)] + delete_private_channel, DeletePrivateChannelRequest, DeletePrivateChannelResponse; + #[rate(3, 15)] + join_private_channel, JoinPrivateChannelRequest, JoinPrivateChannelResponse; + #[rate(3, 5)] + leave_private_channel, LeavePrivateChannelRequest, LeavePrivateChannelResponse; + #[rate(5, 5)] + get_private_channel_list, GetPrivateChannelListRequest, GetPrivateChannelListResponse; + #[rate(3, 8)] + update_private_channel, UpdatePrivateChannelRequest, UpdatePrivateChannelResponse; + #[rate(4, 8)] invite_user_to_guild, InviteUserToGuildRequest, InviteUserToGuildResponse; + #[rate(5 ,5)] get_pending_invites, GetPendingInvitesRequest, GetPendingInvitesResponse; + #[rate(3, 5)] reject_pending_invite, RejectPendingInviteRequest, RejectPendingInviteResponse; + #[rate(3, 5)] ignore_pending_invite, IgnorePendingInviteRequest, IgnorePendingInviteResponse; } @@ -574,14 +813,73 @@ impl ChatTree { }) } - pub async fn is_user_in_guild(&self, guild_id: u64, user_id: u64) -> ServerResult<()> { + pub async fn check_channel_user( + &self, + guild_id: Option, + user_id: u64, + channel_id: u64, + ) -> ServerResult<()> { + if let Some(guild_id) = guild_id { + self.check_guild_user_channel(guild_id, user_id, channel_id) + .await + } else { + self.check_private_channel_user(channel_id, user_id).await + } + } + + pub async fn is_user_in_private_channel( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult { + self.contains_key(&make_member_key_pc(channel_id, user_id)) + .await + .map_err(Into::into) + } + + pub async fn check_user_in_private_channel( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult<()> { + self.is_user_in_private_channel(channel_id, user_id) + .await? + .then(|| Ok(())) + .unwrap_or(Err(ServerError::UserNotInPrivateChannel { + channel_id, + user_id, + })) + .map_err(Into::into) + } + + pub async fn is_user_in_guild(&self, guild_id: u64, user_id: u64) -> ServerResult { self.contains_key(&make_member_key(guild_id, user_id)) + .await + .map_err(Into::into) + } + + pub async fn check_user_in_guild(&self, guild_id: u64, user_id: u64) -> ServerResult<()> { + self.is_user_in_guild(guild_id, user_id) .await? .then(|| Ok(())) .unwrap_or(Err(ServerError::UserNotInGuild { guild_id, user_id })) .map_err(Into::into) } + pub async fn does_private_channel_exist(&self, channel_id: u64) -> ServerResult { + self.contains_key(&make_pc_key(channel_id)) + .await + .map_err(Into::into) + } + + pub async fn check_private_channel_exist(&self, channel_id: u64) -> ServerResult<()> { + self.does_private_channel_exist(channel_id) + .await? + .then(|| Ok(())) + .unwrap_or(Err(ServerError::NoSuchPrivateChannel(channel_id))) + .map_err(Into::into) + } + pub async fn does_guild_exist(&self, guild_id: u64) -> ServerResult<()> { self.contains_key(&guild_id.to_be_bytes()) .await? @@ -631,10 +929,6 @@ impl ChatTree { guild_id: u64, user_id: u64, ) -> Result { - if user_id == 0 { - return Ok(true); - } - let is_owner = self .get_guild_owners(guild_id) .await? @@ -644,6 +938,39 @@ impl ChatTree { Ok(is_owner) } + pub async fn get_private_channel_creator(&self, channel_id: u64) -> ServerResult { + let raw = self + .get(make_pc_creator_key(channel_id)) + .await? + .ok_or("private channels must have a creator")?; + Ok(db::deser_id(raw)) + } + + pub async fn is_user_private_channel_creator( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult { + self.get_private_channel_creator(channel_id) + .await + .map(|creator_id| creator_id == user_id) + } + + pub async fn check_private_channel_creator( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult<()> { + self.is_user_private_channel_creator(channel_id, user_id) + .await? + .then(|| Ok(())) + .unwrap_or(Err(( + "h.not-channel-creator", + "you are not the private channel creator", + ))) + .map_err(Into::into) + } + pub async fn check_guild_user_channel( &self, guild_id: u64, @@ -657,7 +984,7 @@ impl ChatTree { pub async fn check_guild_user(&self, guild_id: u64, user_id: u64) -> ServerResult<()> { self.check_guild(guild_id).await?; - self.is_user_in_guild(guild_id, user_id).await + self.check_user_in_guild(guild_id, user_id).await } #[inline(always)] @@ -665,12 +992,23 @@ impl ChatTree { self.does_guild_exist(guild_id).await } + pub async fn check_private_channel_user( + &self, + channel_id: u64, + user_id: u64, + ) -> ServerResult<()> { + self.check_private_channel_exist(channel_id).await?; + self.check_user_in_private_channel(channel_id, user_id) + .await?; + Ok(()) + } + pub async fn get_message_logic( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, message_id: u64, - ) -> ServerResult<(HarmonyMessage, [u8; 26])> { + ) -> ServerResult<(HarmonyMessage, Vec)> { let key = make_msg_key(guild_id, channel_id, message_id); let message = if let Some(msg) = self.get(&key).await? { @@ -687,31 +1025,76 @@ impl ChatTree { Ok((message, key)) } + pub async fn get_user_local_private_channels( + &self, + user_id: u64, + ) -> ServerResult> { + let prefix = make_pc_list_key_prefix(user_id); + self.get_user_pc_guilds(prefix, |channel_id, host| { + host.is_empty().then(|| PrivateChannelListEntry { + channel_id, + server_id: None, + }) + }) + .await + } + + pub async fn get_user_private_channels( + &self, + user_id: u64, + ) -> ServerResult> { + let prefix = make_pc_list_key_prefix(user_id); + self.get_user_pc_guilds(prefix, |channel_id, host| { + Some(PrivateChannelListEntry { + channel_id, + server_id: Some(host.to_string()), + }) + }) + .await + } + + pub async fn get_user_local_guilds(&self, user_id: u64) -> ServerResult> { + let prefix = make_guild_list_key_prefix(user_id); + self.get_user_pc_guilds(prefix, |guild_id, host| { + host.is_empty().then(|| GuildListEntry { + guild_id, + server_id: None, + }) + }) + .await + } + pub async fn get_user_guilds(&self, user_id: u64) -> ServerResult> { let prefix = make_guild_list_key_prefix(user_id); - let guild_list = self + self.get_user_pc_guilds(prefix, |guild_id, host| { + Some(GuildListEntry { + guild_id, + server_id: host.is_empty().not().then(|| host.to_string()), + }) + }) + .await + } + + pub async fn get_user_pc_guilds( + &self, + prefix: impl AsRef<[u8]>, + f: impl Fn(u64, &str) -> Option, + ) -> ServerResult> { + let prefix = prefix.as_ref(); + let list = self .scan_prefix(&prefix) .await .try_fold(Vec::new(), |mut all, res| { - let (guild_id_raw, _) = res?; - let (id_raw, host_raw) = guild_id_raw - .split_at(prefix.len()) - .1 - .split_at(size_of::()); - - // Safety: this unwrap can never cause UB since we split at u64 boundary - let guild_id = u64::from_be_bytes(unsafe { id_raw.try_into().unwrap_unchecked() }); - // Safety: we never store non UTF-8 hosts, so this can't cause UB - let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; - - all.push(GuildListEntry { - guild_id, - server_id: host.to_string(), - }); + let (id_raw, _) = res?; + let (id, host) = deserialize_id_host(&id_raw, prefix.len()); + + if let Some(item) = f(id, host) { + all.push(item); + } ServerResult::Ok(all) })?; - Ok(guild_list) + Ok(list) } pub async fn get_guild_logic(&self, guild_id: u64) -> ServerResult { @@ -926,7 +1309,7 @@ impl ChatTree { pub async fn get_channel_messages_logic( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, message_id: Option, direction: Option, @@ -977,8 +1360,11 @@ impl ChatTree { .try_fold(Vec::new(), |mut all, res| { let (key, value) = res.map_err(ServerError::from)?; // Safety: this is safe since the only keys we get are message keys, which after stripping prefix are message IDs - let message_id = - deser_id(key.split_at(make_msg_prefix(guild_id, channel_id).len()).1); + let prefix_len = guild_id.map_or_else( + || make_msg_prefix_pc(channel_id).len(), + |guild_id| make_msg_prefix(guild_id, channel_id).len(), + ); + let message_id = deser_id(key.split_at(prefix_len).1); let message = db::deser_message(value); all.push(MessageWithId { message_id, @@ -1011,7 +1397,7 @@ impl ChatTree { })) } - pub async fn query_has_permission_logic( + pub async fn has_permission_logic( &self, guild_id: u64, channel_id: Option, @@ -1046,14 +1432,20 @@ impl ChatTree { Ok(false) } + // TODO: make this return bool so we can differ db errors + // from actual permission error pub async fn check_perms( &self, - guild_id: u64, + guild_id: impl Into>, channel_id: Option, user_id: u64, check_for: &str, must_be_guild_owner: bool, ) -> Result<(), ServerError> { + let Some(guild_id) = guild_id.into() else { + // For private channels, everyone in the channel can do anything + return Ok(()); + }; let is_owner = self.is_user_guild_owner(guild_id, user_id).await?; if must_be_guild_owner { if is_owner { @@ -1061,7 +1453,7 @@ impl ChatTree { } } else if is_owner || self - .query_has_permission_logic(guild_id, channel_id, user_id, check_for) + .has_permission_logic(guild_id, channel_id, user_id, check_for) .await? { return Ok(()); @@ -1195,7 +1587,7 @@ impl ChatTree { let mut batch = Batch::default(); batch.insert(key, buf); batch.insert( - make_next_msg_id_key(guild_id, channel_id), + make_next_msg_id_key_guild(guild_id, channel_id), 1_u64.to_be_bytes(), ); self.chat_tree.apply_batch(batch).await?; @@ -1213,7 +1605,6 @@ impl ChatTree { name: String, picture: Option, metadata: Option, - kind: guild_kind::Kind, ) -> ServerResult { let guild_id = { let mut rng = rand::rngs::SmallRng::from_entropy(); @@ -1229,7 +1620,6 @@ impl ChatTree { picture, owner_ids: vec![user_id], metadata, - kind: Some(GuildKind { kind: Some(kind) }), }; let buf = rkyv_ser(&guild); @@ -1284,6 +1674,141 @@ impl ChatTree { Ok(guild_id) } + #[inline] + pub async fn remove_user_pending_invite( + &self, + user_id: u64, + server_id: Option<&str>, + location: &pending_invite::Location, + ) -> ServerResult { + self.modify_user_pending_invites(user_id, |invites| { + if let Some(pos) = invites.iter().position(|inv| { + inv.server_id.as_deref() == server_id && inv.location.as_ref() == Some(location) + }) { + Ok(invites.remove(pos)) + } else { + Err(("h.no-such-pending-invite", "no such pending invite found").into()) + } + }) + .await + } + + #[inline] + pub async fn add_user_pending_invite( + &self, + user_id: u64, + invite: PendingInvite, + ) -> ServerResult<()> { + self.modify_user_pending_invites(user_id, |invites| Ok(invites.push(invite))) + .await + } + + pub async fn modify_user_pending_invites( + &self, + user_id: u64, + f: impl FnOnce(&mut Vec) -> ServerResult, + ) -> ServerResult { + let key = make_user_pending_invites_key(user_id); + + let mut pending = self + .get(key) + .await? + .map_or_else(UserPendingInvites::default, db::deser_pending_invites); + + let result = f(&mut pending.invites)?; + + let pending_ser = rkyv_ser(&pending); + self.insert(key, pending_ser).await?; + + Ok(result) + } + + pub async fn use_priv_invite_logic(&self, user_id: u64, invite_id: &str) -> ServerResult { + let allowed_users_key = make_priv_invite_allowed_key(&invite_id); + let mut allowed_users = self.get_list_u64_logic(&allowed_users_key).await?; + + if let Some(pos) = allowed_users.iter().position(|id| user_id.eq(id)) { + allowed_users.remove(pos); + } else { + bail!(( + "h.not-invited", + "you can't use this invite because you aren't invited" + )); + } + + let Some(invite_raw) = self.get(make_priv_invite_key(&invite_id)).await? else { + bail!(ServerError::NoSuchInvite(invite_id.into())); + }; + + let (id, mut invite) = deser_invite_entry(invite_raw); + invite.use_count += 1; + + let invite_buf = rkyv_ser(&invite); + + let mut batch = Batch::default(); + batch.insert( + make_priv_invite_key(&invite_id), + [id.to_be_bytes().as_ref(), invite_buf.as_ref()].concat(), + ); + batch.insert( + allowed_users_key, + self.serialize_list_u64_logic(allowed_users), + ); + self.apply_batch(batch).await?; + + Ok(id) + } + + // returns new allowed users + pub async fn update_priv_invite_allowed_users( + &self, + invite_id: String, + f: impl FnOnce(&mut Vec), + ) -> ServerResult> { + let key = make_priv_invite_allowed_key(&invite_id); + + let mut allowed_users = self.get_list_u64_logic(&key).await?; + f(&mut allowed_users); + + let serialized = self.serialize_list_u64_logic(allowed_users.clone()); + self.insert(key, serialized).await?; + + Ok(allowed_users) + } + + // returns invite id + pub async fn create_priv_invite_logic( + &self, + id: u64, + mut users_allowed: Vec, + use_id_as_invite_id: bool, + ) -> ServerResult { + users_allowed.dedup(); + + let invite_id = use_id_as_invite_id + .then(|| id.to_string()) + .unwrap_or_else(|| gen_rand_inline_str().to_string()); + + let invite = Invite { + possible_uses: users_allowed.len() as u32, + use_count: 0, + }; + let buf = rkyv_ser(&invite); + + let mut batch = Batch::default(); + batch.insert( + make_priv_invite_key(&invite_id), + [id.to_be_bytes().as_ref(), buf.as_ref()].concat(), + ); + batch.insert( + make_priv_invite_allowed_key(&invite_id), + self.serialize_list_u64_logic(users_allowed), + ); + self.apply_batch(batch).await?; + + Ok(invite_id) + } + pub async fn create_invite_logic( &self, guild_id: u64, @@ -1331,23 +1856,42 @@ impl ChatTree { Ok(all) } + /// Adds a private channel to a user's private channel list + pub async fn add_pc_to_pc_list( + &self, + user_id: u64, + channel_id: u64, + server_id: &str, + ) -> ServerResult<()> { + self.insert(make_pc_list_key(user_id, channel_id, server_id), []) + .await?; + Ok(()) + } + + /// Removes a private channel from a user's private channel list + pub async fn remove_pc_from_pc_list( + &self, + user_id: u64, + channel_id: u64, + server_id: &str, + ) -> ServerResult<()> { + self.chat_tree + .remove(&make_pc_list_key(user_id, channel_id, server_id)) + .await + .map(|_| ()) + .map_err(ServerError::from) + .map_err(Into::into) + } + /// Adds a guild to a user's guild list pub async fn add_guild_to_guild_list( &self, user_id: u64, guild_id: u64, - homeserver: &str, + server_id: &str, ) -> ServerResult<()> { - self.insert( - [ - make_guild_list_key_prefix(user_id).as_ref(), - guild_id.to_be_bytes().as_ref(), - homeserver.as_bytes(), - ] - .concat(), - [], - ) - .await?; + self.insert(make_guild_list_key(user_id, guild_id, server_id), []) + .await?; Ok(()) } @@ -1356,10 +1900,10 @@ impl ChatTree { &self, user_id: u64, guild_id: u64, - homeserver: &str, + server_id: &str, ) -> ServerResult<()> { self.chat_tree - .remove(&make_guild_list_key(user_id, guild_id, homeserver)) + .remove(&make_guild_list_key(user_id, guild_id, server_id)) .await .map(|_| ()) .map_err(ServerError::from) @@ -1425,12 +1969,12 @@ impl ChatTree { } } - pub async fn query_has_permission_request( + pub async fn has_permission_request( &self, user_id: u64, - request: QueryHasPermissionRequest, - ) -> ServerResult { - let QueryHasPermissionRequest { + request: HasPermissionRequest, + ) -> ServerResult { + let HasPermissionRequest { guild_id, channel_id, check_for, @@ -1450,17 +1994,21 @@ impl ChatTree { return Err(ServerError::EmptyPermissionQuery.into()); } - Ok(QueryHasPermissionResponse { - ok: self + let mut perms = Vec::with_capacity(check_for.len()); + for check_for in check_for { + let ok = self .check_perms(guild_id, channel_id, check_as, &check_for, false) .await - .is_ok(), - }) + .is_ok(); + perms.push(Permission::new(check_for, ok)); + } + + Ok(HasPermissionResponse::new(perms)) } pub async fn get_next_message_id( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, ) -> Result { let next_id_key = make_next_msg_id_key(guild_id, channel_id); @@ -1473,12 +2021,11 @@ impl ChatTree { pub async fn get_last_message_id( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, ) -> Result { let next_id_key = make_next_msg_id_key(guild_id, channel_id); let raw = self - .chat_tree .get(&next_id_key) .await? .expect("no next message id for channel - this is a bug"); @@ -1487,21 +2034,18 @@ impl ChatTree { Ok(id) } + #[allow(clippy::too_many_arguments)] pub async fn send_message_logic( &self, + guild_id: Option, + channel_id: u64, user_id: u64, - request: SendMessageRequest, + content: Content, + overrides: Option, + in_reply_to: Option, + metadata: Option, + actions: Vec, ) -> ServerResult<(u64, HarmonyMessage)> { - let SendMessageRequest { - guild_id, - channel_id, - content, - echo_id: _, - overrides, - in_reply_to, - metadata, - } = request; - let message_id = self.get_next_message_id(guild_id, channel_id).await?; let key = make_msg_key(guild_id, channel_id, message_id); // [tag:msg_key_u64] @@ -1513,9 +2057,10 @@ impl ChatTree { author_id: user_id, created_at, edited_at, - content, + content: Some(content), in_reply_to, overrides, + actions, reactions: Vec::new(), }; @@ -1525,7 +2070,7 @@ impl ChatTree { Ok((message_id, message)) } - pub fn process_message_overrides(&self, overrides: Option<&Overrides>) -> ServerResult<()> { + pub fn check_message_overrides(&self, overrides: Option<&Overrides>) -> ServerResult<()> { if let Some(ov) = overrides { if ov.username.as_ref().map_or(false, String::is_empty) { bail!(( @@ -1546,249 +2091,223 @@ impl ChatTree { Ok(()) } - pub async fn process_message_content( + pub async fn process_image_info( &self, - content: Option, - media_root: &Path, - host: &str, - ) -> ServerResult { - use content::Content as MsgContent; - - let inner_content = content.and_then(|c| c.content); - let content = if let Some(content) = inner_content { - let content = match content { - content::Content::TextMessage(text) => { - if text.content.as_ref().map_or(true, |f| f.text.is_empty()) { - bail!(ServerError::MessageContentCantBeEmpty); - } - content::Content::TextMessage(text) - } - content::Content::PhotoMessage(mut photos) => { - if photos.photos.is_empty() { - bail!(ServerError::MessageContentCantBeEmpty); - } - for photo in photos.photos.drain(..).collect::>() { - // TODO: return error for invalid hmc - if let Ok(file_id) = FileId::from_str(&photo.hmc) { - // TODO: check if the hmc host matches ours, if not fetch the image from the other host - let id = match &file_id { - FileId::External(_) => bail!(( - "h.photo-cant-have-external-url", - "message photo contents cant use external URL" - )), - FileId::Hmc(hmc) => hmc.id(), - FileId::Id(id) => id.as_str(), - }; + deps: &Dependencies, + info: SendImageInfo, + id: String, + file_handle: FileHandle, + ) -> Result<(String, ImageInfo), ServerError> { + let SendImageInfo { + caption, + use_original, + } = info; + + let media_root = &deps.config.media.media_root; + let get_format = |path: &Path| { + Result::<_, ServerError>::Ok( + infer::get_from_path(path)? + .and_then(|t| ImageFormat::from_mime_type(t.mime_type())) + .expect("unsupported image format"), + ) + }; - let image_id = format!("{}_{}", id, "jpeg"); - let image_path = media_root.join(&image_id); - let minithumbnail_id = format!("{}_{}", id, "jpegthumb"); - let minithumbnail_path = media_root.join(&minithumbnail_id); - - let ((file_size, isize), minithumbnail) = - if image_path.exists() && minithumbnail_path.exists() { - let minithumbnail = tokio::fs::read(&minithumbnail_path) - .await - .map_err(ServerError::from)?; - - let ifile = tokio::fs::File::open(&image_path) - .await - .map_err(ServerError::from)?; - - let file_size = - ifile.metadata().await.map_err(ServerError::from)?.len(); - let ifile = BufReader::new(ifile.into_std().await); - let ireader = image::io::Reader::new(ifile) - .with_guessed_format() - .map_err(ServerError::from)?; - // this should be cheap and shouldnt block... - let isize = ireader - .into_dimensions() - .map_err(|_| ServerError::InternalServerError)?; - - let mut minithumbnail = std::io::Cursor::new(minithumbnail); - let minireader = image::io::Reader::new(&mut minithumbnail) - .with_guessed_format() - .map_err(ServerError::from)?; - let minisize = minireader - .into_dimensions() - .map_err(|_| ServerError::InternalServerError)?; - - ( - (file_size as u32, isize), - Minithumbnail { - width: minisize.0, - height: minisize.1, - data: minithumbnail.into_inner(), - }, - ) - } else { - let (_, mime, data, _) = get_file_full(media_root, id).await?; - - let is_animated = mime == "image/gif"; - - let (minithumbnail, image_size, image_raw, data) = - tokio::task::spawn_blocking(move || { - let image = (mime == "image/webp") - .then(|| { - let decoder = webp::Decoder::new(&data); - decoder.decode().map(|i| i.to_image()) - }) - .flatten() - .or_else(|| image::load_from_memory(&data).ok()) - .ok_or(ServerError::InternalServerError)?; - - let image_size = image.dimensions(); - let minithumbnail = image.thumbnail(64, 64); - let encoded = is_animated.not().then(|| { - let rgba = image.into_rgba8(); - let encoder = webp::Encoder::from_rgba( - rgba.as_ref(), - rgba.width(), - rgba.height(), - ); - encoder.encode(100.0) - }); - ServerResult::Ok(( - minithumbnail, - image_size, - encoded.map(|m| m.to_vec()), - data, - )) - }) - .await - .map_err(|_| ServerError::InternalServerError)??; - - let image_raw = image_raw.unwrap_or(data); - - tokio::fs::write(&image_path, &image_raw) - .await - .map_err(ServerError::from)?; - - let (minithumb_size, minithumbnail_raw) = - tokio::task::spawn_blocking(move || { - let rgba = minithumbnail.into_rgba8(); - let encoder = webp::Encoder::from_rgba( - rgba.as_ref(), - rgba.width(), - rgba.height(), - ); - let encoded = encoder.encode(100.0); - - ServerResult::Ok(((64, 64), encoded.to_vec())) - }) - .await - .map_err(|_| ServerError::InternalServerError)??; - - tokio::fs::write(&minithumbnail_path, &minithumbnail_raw) - .await - .map_err(ServerError::from)?; - - ( - (image_raw.len() as u32, image_size), - Minithumbnail { - width: minithumb_size.0, - height: minithumb_size.1, - data: minithumbnail_raw, - }, - ) - }; - - photos.photos.push(Photo { - hmc: Hmc::new(host, image_id).unwrap().into(), - minithumbnail: Some(minithumbnail), - width: isize.0, - height: isize.1, - file_size, - ..photo - }); - } else { - photos.photos.push(photo); - } - } - content::Content::PhotoMessage(photos) - } - content::Content::AttachmentMessage(mut files) => { - if files.files.is_empty() { - bail!(ServerError::MessageContentCantBeEmpty); - } - for attachment in files.files.drain(..).collect::>() { - if let Ok(id) = FileId::from_str(&attachment.id) { - let fill_file_local = move |attachment: Attachment, id: String| async move { - let is_jpeg = is_id_jpeg(&id); - let (mut file, metadata, path) = - get_file_handle(media_root, &id).await?; - let (filename_raw, mimetype_raw, _) = - read_bufs(&path, &mut file, is_jpeg).await?; - let (start, end) = calculate_range( - &filename_raw, - &mimetype_raw, - &metadata, - is_jpeg, - ); - let size = end - start; - - Result::<_, ServerError>::Ok(Attachment { - name: attachment - .name - .is_empty() - .then(|| unsafe { - String::from_utf8_unchecked(filename_raw) - }) - .unwrap_or(attachment.name), - size: attachment - .size - .eq(&0) - .then(|| size as u32) - .unwrap_or(attachment.size), - mimetype: attachment - .mimetype - .is_empty() - .then(|| unsafe { - String::from_utf8_unchecked(mimetype_raw) - }) - .unwrap_or(attachment.mimetype), - ..attachment - }) - }; - match id { - FileId::Hmc(hmc) => { - // TODO: fetch file from remote host if its not local - let id = hmc.id(); - files - .files - .push(fill_file_local(attachment, id.to_string()).await?); - } - FileId::Id(id) => { - files.files.push(fill_file_local(attachment, id).await?) - } - _ => files.files.push(attachment), - } - } else { - files.files.push(attachment); - } - } - content::Content::AttachmentMessage(files) - } - content::Content::EmbedMessage(embed) => { - if embed.embeds.is_empty() { - bail!(ServerError::MessageContentCantBeEmpty); - } - content::Content::EmbedMessage(embed) + // TODO: improve this code + let minithumbnail_id = format!("{}_jpegthumb", id); + let minithumbnail_path = media_root.join(&minithumbnail_id); + + let id = use_original + .not() + .then(|| format!("{}_jpeg", id)) + .unwrap_or(id); + + let image_path = media_root.join(&id); + let image_format = + ImageFormat::from_mime_type(&file_handle.mime).expect("unsupported image format"); + + let is_animated = image_format == ImageFormat::Gif; + let is_webp = image_format == ImageFormat::WebP; + + let id_ = id.clone(); + let task_fn = move || -> Result<_, ServerError> { + let _guard = + tracing::debug_span!("image_processing", id = %id_, format = ?image_format) + .entered(); + if image_path.exists() && minithumbnail_path.exists() { + use image::io::Reader as ImageReader; + + tracing::debug!("loading existing processed image and thumbnail"); + + let image_format = get_format(&image_path)?; + let ifile = BufReader::new(std::fs::File::open(&image_path)?); + let mut ireader = ImageReader::new(ifile); + ireader.set_format(image_format); + let idimensions = ireader + .into_dimensions() + .map_err(ServerError::ImageProcessError)?; + + let minithumbnail_format = get_format(&minithumbnail_path)?; + let mut minireader = ImageReader::open(&minithumbnail_path)?; + minireader.set_format(minithumbnail_format); + let minisize = minireader + .into_dimensions() + .map_err(ServerError::ImageProcessError)?; + + let minithumbnail = Minithumbnail { + width: minisize.0, + height: minisize.1, + data: std::fs::read(&minithumbnail_path)?, + }; + + Ok((idimensions, minithumbnail)) + } else { + tracing::debug!("loading original image and processing"); + + let raw = file_handle.read_blocking()?; + + let image = is_webp + .then(|| { + let decoder = webp::Decoder::new(&raw); + decoder.decode().map(|i| i.to_image()) + }) + .flatten(); + let image = match image { + Some(img) => img, + None => image::load_from_memory_with_format(&raw, image_format) + .map_err(ServerError::ImageProcessError)?, + }; + + let image_size = image.dimensions(); + let minithumbnail = image.thumbnail(64, 64); + if use_original.not() { + let encoded = is_animated.not().then(|| { + let rgba = image.into_rgba8(); + let encoder = + webp::Encoder::from_rgba(rgba.as_ref(), rgba.width(), rgba.height()); + encoder.encode(100.0) + }); + let image_raw = encoded.map(|m| m.to_vec()).unwrap_or(raw); + std::fs::write(&image_path, &image_raw)?; } - MsgContent::InviteAccepted(_) - | MsgContent::InviteRejected(_) - | MsgContent::RoomUpgradedToGuild(_) => { - bail!(ServerError::ContentCantBeSentByUser); + + let rgba = minithumbnail.into_rgba8(); + let encoder = webp::Encoder::from_rgba(rgba.as_ref(), rgba.width(), rgba.height()); + let encoded = encoder.encode(100.0).to_vec(); + + std::fs::write(&minithumbnail_path, &encoded)?; + + Ok(( + image_size, + Minithumbnail { + width: rgba.width(), + height: rgba.height(), + data: encoded, + }, + )) + } + }; + + let ((width, height), minithumbnail) = tokio::task::spawn_blocking(task_fn) + .await + .expect("image process task panicked")?; + + let info = ImageInfo { + width, + height, + minithumbnail: Some(minithumbnail), + caption, + }; + + Ok((id, info)) + } + + pub async fn process_attachment_info( + &self, + deps: &Dependencies, + info: Option, + id: String, + file_handle: FileHandle, + ) -> Result<(String, Option), ServerError> { + use send_message_request::attachment::Info; + + if let Some(info) = info { + let (id, info) = match info { + Info::Image(info) => { + let (id, info) = self.process_image_info(deps, info, id, file_handle).await?; + + (id, attachment::Info::Image(info)) } }; - Content { - content: Some(content), - } + + Ok((id, Some(info))) + } else if file_handle.mime != "image/svg+xml" && file_handle.mime.starts_with("image") { + let (id, info) = self + .process_image_info(deps, SendImageInfo::default(), id, file_handle) + .await?; + Ok((id, Some(attachment::Info::Image(info)))) } else { + Ok((id, None)) + } + } + + pub async fn process_attachment( + &self, + deps: &Dependencies, + send_message_request::Attachment { id, name, info }: send_message_request::Attachment, + ) -> ServerResult { + let file_handle = deps.media.get_file(&id).await?; + + let size = file_handle.size; + let filename = name + .is_empty() + .then(|| file_handle.name.clone()) + .unwrap_or(name); + let mimetype = file_handle.mime.clone(); + + let (id, info) = self + .process_attachment_info(deps, info, id, file_handle) + .await?; + + Ok(Attachment { + id, + name: filename, + mimetype, + size: size as u32, + info, + }) + } + + pub async fn process_message_content( + &self, + deps: &Dependencies, + content: Option, + ) -> ServerResult { + let Some(content) = content else { bail!(ServerError::MessageContentCantBeEmpty); }; + let is_text_empty = content.text.is_empty(); + let is_embeds_empty = content.embeds.is_empty(); + let is_attachments_empty = content.attachments.is_empty(); + + // check if message content is empty + if is_attachments_empty && is_embeds_empty && is_text_empty { + bail!(ServerError::MessageContentCantBeEmpty); + } + + let mut attachments = Vec::with_capacity(content.attachments.len()); + for attachment in content.attachments { + attachments.push(self.process_attachment(deps, attachment).await?); + } + + let content = Content { + text: content.text, + text_formats: content.text_formats, + embeds: content.embeds, + attachments, + extra: None, + }; + Ok(content) } @@ -1800,90 +2319,141 @@ impl ChatTree { pub async fn send_with_system( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, - content: content::Content, + content: Content, ) -> ServerResult<(u64, HarmonyMessage)> { - let request = SendMessageRequest::default() - .with_guild_id(guild_id) - .with_channel_id(channel_id) - .with_content(Content { - content: Some(content), - }) - .with_overrides(Overrides { - username: Some("System".to_string()), - reason: Some(overrides::Reason::SystemMessage(Empty {})), - avatar: None, - }); - self.send_message_logic(0, request).await + let overrides = Overrides { + username: Some("System".to_string()), + reason: Some(overrides::Reason::SystemMessage(Empty {})), + avatar: None, + }; + self.send_message_logic( + guild_id, + channel_id, + 0, + content, + Some(overrides), + None, + None, + Vec::new(), + ) + .await } - pub async fn update_reaction( + /// Removes a reaction by decrementing the count, if count is zero + /// deletes the reaction from message reactions. + /// + /// Returns `true` if the message was deleted from message reactions. + pub async fn remove_reaction( &self, user_id: u64, - guild_id: u64, + guild_id: Option, channel_id: u64, message_id: u64, - emote: Emote, - add: bool, - ) -> ServerResult> { - let react_key = - make_user_reacted_msg_key(guild_id, channel_id, message_id, user_id, &emote.image_id); + data: &str, + ) -> ServerResult { + let react_key = guild_id.map_or_else( + || make_user_reacted_msg_key_pc(channel_id, message_id, user_id, data), + |guild_id| make_user_reacted_msg_key(guild_id, channel_id, message_id, user_id, data), + ); + let reacted = self .chat_tree .contains_key(&react_key) .await .map_err(ServerError::from)?; - if matches!((add, reacted), (true, true) | (false, false)) { - return Ok(None); + if reacted.not() { + bail!(("h.cant-remove-react", "cant remove react if didnt react")); } // TODO: validate the emote image_id is below a certain size - self.check_guild_user_channel(guild_id, user_id, channel_id) + let (mut message, message_key) = self + .get_message_logic(guild_id, channel_id, message_id) .await?; + let remove_index = message + .reactions + .iter_mut() + .enumerate() + .find(|(_, r)| r.data == data) + .and_then(|(index, reaction)| { + reaction.count = reaction.count.saturating_sub(1); + (reaction.count == 0).then(|| index) + }); + + if let Some(index) = remove_index { + message.reactions.remove(index); + } + + self.insert(message_key, rkyv_ser(&message)).await?; + self.remove(react_key).await?; + + Ok(remove_index.is_some()) + } + + /// Adds a new reaction by incrementing the count, if the reaction + /// doesn't already exist adds it to the message reactions. + /// + /// Returns `true` if the reaction was new. + #[allow(clippy::too_many_arguments)] + pub async fn add_reaction( + &self, + user_id: u64, + guild_id: Option, + channel_id: u64, + message_id: u64, + data: String, + name: String, + kind: ReactionKind, + ) -> ServerResult { + let react_key = guild_id.map_or_else( + || make_user_reacted_msg_key_pc(channel_id, message_id, user_id, &data), + |guild_id| make_user_reacted_msg_key(guild_id, channel_id, message_id, user_id, &data), + ); + + let reacted = self + .chat_tree + .contains_key(&react_key) + .await + .map_err(ServerError::from)?; + if reacted { + bail!(("h.cant-react-multiple", "cant react multiple times")); + } + + // TODO: validate the emote image_id is below a certain size let (mut message, message_key) = self .get_message_logic(guild_id, channel_id, message_id) .await?; - let mut batch = Batch::default(); - let reaction = if let Some(reaction) = message.reactions.iter_mut().find(|r| { - r.emote - .as_ref() - .map_or(false, |e| e.image_id == emote.image_id) - }) { - reaction.count = add - .then(|| reaction.count.saturating_add(1)) - .unwrap_or_else(|| reaction.count.saturating_sub(1)); - if reaction.count == 0 { - batch.remove(react_key); - } - Some(reaction.clone()) - } else if add { + let did_increment = message + .reactions + .iter_mut() + .find(|r| r.data == data) + .map(|reaction| { + reaction.count = reaction.count.saturating_add(1); + }) + .is_some(); + + if did_increment.not() { let reaction = Reaction { count: 1, - emote: Some(emote), + data, + kind: kind.into(), + name, }; - batch.insert(react_key, Vec::new()); - message.reactions.push(reaction.clone()); - Some(reaction) - } else { - None - }; + message.reactions.push(reaction); + } - batch.insert(message_key, rkyv_ser(&message)); + self.insert(message_key, rkyv_ser(&message)).await?; + self.insert(react_key, []).await?; - self.chat_tree - .apply_batch(batch) - .await - .map_err(ServerError::from)?; - - Ok(reaction) + Ok(did_increment.not()) } pub async fn get_pinned_messages_logic( &self, - guild_id: u64, + guild_id: Option, channel_id: u64, ) -> Result, ServerError> { let pinned_msgs_raw = self.get(make_pinned_msgs_key(guild_id, channel_id)).await?; @@ -1899,3 +2469,16 @@ impl ChatTree { Ok(()) } } + +// used for deserializing private channel or guild entries from the db +// this function assumes that the passed id_raw is of structure `prefix + u64 + utf-8 string` +fn deserialize_id_host(id_raw: &[u8], prefix_len: usize) -> (u64, &str) { + let (id_raw, host_raw) = id_raw.split_at(prefix_len).1.split_at(size_of::()); + + // Safety: this unwrap can never cause UB since we split at u64 boundary + let guild_id = u64::from_be_bytes(unsafe { id_raw.try_into().unwrap_unchecked() }); + // Safety: we never store non UTF-8 hosts, so this can't cause UB + let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; + + (guild_id, host) +} diff --git a/src/impls/chat/moderation/ban_user.rs b/src/impls/chat/moderation/ban_user.rs index 9f8eadf..fc3eb8a 100644 --- a/src/impls/chat/moderation/ban_user.rs +++ b/src/impls/chat/moderation/ban_user.rs @@ -12,13 +12,13 @@ pub async fn handler( } = request.into_message().await?; if user_id == user_to_ban { - return Err(ServerError::CantKickOrBanYourself.into()); + bail!(ServerError::CantKickOrBanYourself); } let chat_tree = &svc.deps.chat_tree; chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree.is_user_in_guild(guild_id, user_to_ban).await?; + chat_tree.check_user_in_guild(guild_id, user_to_ban).await?; chat_tree .check_perms(guild_id, None, user_id, "user.manage.ban", false) .await?; @@ -32,7 +32,7 @@ pub async fn handler( ) .await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::LeftMember(stream_event::MemberLeft { guild_id, @@ -45,5 +45,5 @@ pub async fn handler( svc.dispatch_guild_leave(guild_id, user_to_ban).await?; - Ok((BanUserResponse {}).into_response()) + Ok(BanUserResponse::new().into_response()) } diff --git a/src/impls/chat/moderation/kick_user.rs b/src/impls/chat/moderation/kick_user.rs index 9ce4ca8..c3dc19c 100644 --- a/src/impls/chat/moderation/kick_user.rs +++ b/src/impls/chat/moderation/kick_user.rs @@ -12,20 +12,22 @@ pub async fn handler( } = request.into_message().await?; if user_id == user_to_kick { - return Err(ServerError::CantKickOrBanYourself.into()); + bail!(ServerError::CantKickOrBanYourself); } let chat_tree = &svc.deps.chat_tree; chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree.is_user_in_guild(guild_id, user_to_kick).await?; + chat_tree + .check_user_in_guild(guild_id, user_to_kick) + .await?; chat_tree .check_perms(guild_id, None, user_id, "user.manage.kick", false) .await?; chat_tree.kick_user_logic(guild_id, user_to_kick).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::LeftMember(stream_event::MemberLeft { guild_id, @@ -38,5 +40,5 @@ pub async fn handler( svc.dispatch_guild_leave(guild_id, user_to_kick).await?; - Ok((KickUserResponse {}).into_response()) + Ok(KickUserResponse::new().into_response()) } diff --git a/src/impls/chat/moderation/unban_user.rs b/src/impls/chat/moderation/unban_user.rs index b14ec2f..63bde64 100644 --- a/src/impls/chat/moderation/unban_user.rs +++ b/src/impls/chat/moderation/unban_user.rs @@ -22,5 +22,5 @@ pub async fn handler( .remove(make_banned_member_key(guild_id, user_to_unban)) .await?; - Ok((UnbanUserResponse {}).into_response()) + Ok(UnbanUserResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/add_guild_role.rs b/src/impls/chat/permissions/add_guild_role.rs index 7ddd167..9062abd 100644 --- a/src/impls/chat/permissions/add_guild_role.rs +++ b/src/impls/chat/permissions/add_guild_role.rs @@ -29,7 +29,7 @@ pub async fn handler( pingable, }; let role_id = chat_tree.add_guild_role_logic(guild_id, None, role).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::RoleCreated(stream_event::RoleCreated { guild_id, @@ -39,12 +39,7 @@ pub async fn handler( hoist, pingable, }), - Some(PermCheck::new( - guild_id, - None, - all_permissions::ROLES_GET, - false, - )), + Some(PermCheck::new(guild_id, None, all_permissions::ROLES_GET)), EventContext::empty(), ); diff --git a/src/impls/chat/permissions/delete_guild_role.rs b/src/impls/chat/permissions/delete_guild_role.rs index 34f73f0..a1ec711 100644 --- a/src/impls/chat/permissions/delete_guild_role.rs +++ b/src/impls/chat/permissions/delete_guild_role.rs @@ -22,17 +22,12 @@ pub async fn handler( .map_err(ServerError::DbError)? .ok_or(ServerError::NoSuchRole { guild_id, role_id })?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::RoleDeleted(stream_event::RoleDeleted { guild_id, role_id }), - Some(PermCheck::new( - guild_id, - None, - all_permissions::ROLES_GET, - false, - )), + Some(PermCheck::new(guild_id, None, all_permissions::ROLES_GET)), EventContext::empty(), ); - Ok((DeleteGuildRoleResponse {}).into_response()) + Ok(DeleteGuildRoleResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/get_permissions.rs b/src/impls/chat/permissions/get_permissions.rs index 3e73419..81f33a6 100644 --- a/src/impls/chat/permissions/get_permissions.rs +++ b/src/impls/chat/permissions/get_permissions.rs @@ -1,3 +1,7 @@ +use std::collections::HashMap; + +use harmony_rust_sdk::api::chat::get_permissions_response::Permissions; + use super::*; pub async fn handler( @@ -8,32 +12,51 @@ pub async fn handler( let GetPermissionsRequest { guild_id, - channel_id, + channel_ids, role_id, } = request.into_message().await?; let chat_tree = &svc.deps.chat_tree; + let get_perms = move |channel_id| async move { + let perms = chat_tree + .get_permissions_logic(guild_id, channel_id, role_id) + .await? + .into_iter() + .map(|(m, ok)| Permission { + matches: m.into(), + ok, + }) + .collect::>(); + ServerResult::Ok(Permissions::new(perms)) + }; + let check_perms = move |channel_id| async move { + chat_tree + .check_perms( + guild_id, + channel_id, + user_id, + "permissions.manage.get", + false, + ) + .await + }; chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree - .check_perms( - guild_id, - channel_id, - user_id, - "permissions.manage.get", - false, - ) - .await?; - - let perms = chat_tree - .get_permissions_logic(guild_id, channel_id, role_id) - .await? - .into_iter() - .map(|(m, ok)| Permission { - matches: m.into(), - ok, - }) - .collect(); - - Ok((GetPermissionsResponse { perms }).into_response()) + check_perms(None).await?; + for channel_id in channel_ids.iter().copied() { + check_perms(Some(channel_id)).await?; + } + + let guild_perms = get_perms(None).await?; + let mut channel_perms = HashMap::with_capacity(channel_ids.len()); + for channel_id in channel_ids { + let chan_perms = get_perms(Some(channel_id)).await?; + channel_perms.insert(channel_id, chan_perms); + } + + Ok((GetPermissionsResponse { + guild_perms: Some(guild_perms), + channel_perms, + }) + .into_response()) } diff --git a/src/impls/chat/permissions/get_user_roles.rs b/src/impls/chat/permissions/get_user_roles.rs index fcd1f6a..4e674ec 100644 --- a/src/impls/chat/permissions/get_user_roles.rs +++ b/src/impls/chat/permissions/get_user_roles.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use super::*; pub async fn handler( @@ -8,23 +10,42 @@ pub async fn handler( let GetUserRolesRequest { guild_id, - user_id: user_to_fetch, + user_ids: users_to_fetch, } = request.into_message().await?; let chat_tree = &svc.deps.chat_tree; + let users_to_fetch = users_to_fetch + .is_empty() + .then(|| vec![user_id]) + .unwrap_or(users_to_fetch); + chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree.is_user_in_guild(guild_id, user_to_fetch).await?; - let fetch_user = (user_to_fetch == 0) - .then(|| user_id) - .unwrap_or(user_to_fetch); - if fetch_user != user_id { + let mut fetch_other_user = false; + for user_to_fetch in users_to_fetch.iter().copied() { + if user_to_fetch == user_id { + fetch_other_user = true; + } + chat_tree + .check_user_in_guild(guild_id, user_to_fetch) + .await?; + } + if fetch_other_user { chat_tree .check_perms(guild_id, None, user_id, "roles.user.get", false) .await?; } - let roles = chat_tree.get_user_roles_logic(guild_id, fetch_user).await?; + let mut user_roles = HashMap::with_capacity(users_to_fetch.len()); + for user_to_fetch in users_to_fetch { + let roles = chat_tree + .get_user_roles_logic(guild_id, user_to_fetch) + .await?; + user_roles.insert( + user_to_fetch, + get_user_roles_response::UserRoles::new(roles), + ); + } - Ok((GetUserRolesResponse { roles }).into_response()) + Ok((GetUserRolesResponse::new(user_roles)).into_response()) } diff --git a/src/impls/chat/permissions/give_up_ownership.rs b/src/impls/chat/permissions/give_up_ownership.rs index b166e9e..9e24f41 100644 --- a/src/impls/chat/permissions/give_up_ownership.rs +++ b/src/impls/chat/permissions/give_up_ownership.rs @@ -22,8 +22,8 @@ pub async fn handler( guild.owner_ids.remove(pos); } } else { - return Err(ServerError::MustNotBeLastOwner.into()); + bail!(ServerError::MustNotBeLastOwner); } - Ok((GiveUpOwnershipResponse {}).into_response()) + Ok(GiveUpOwnershipResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/grant_ownership.rs b/src/impls/chat/permissions/grant_ownership.rs index 55b3849..a7d52c9 100644 --- a/src/impls/chat/permissions/grant_ownership.rs +++ b/src/impls/chat/permissions/grant_ownership.rs @@ -14,7 +14,9 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree.is_user_in_guild(guild_id, new_owner_id).await?; + chat_tree + .check_user_in_guild(guild_id, new_owner_id) + .await?; chat_tree .check_perms(guild_id, None, user_id, "", true) @@ -24,5 +26,5 @@ pub async fn handler( guild.owner_ids.push(new_owner_id); chat_tree.put_guild_logic(guild_id, guild).await?; - Ok((GrantOwnershipResponse {}).into_response()) + Ok(GrantOwnershipResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/query_has_permission.rs b/src/impls/chat/permissions/has_permission.rs similarity index 60% rename from src/impls/chat/permissions/query_has_permission.rs rename to src/impls/chat/permissions/has_permission.rs index 0d96917..78e8a25 100644 --- a/src/impls/chat/permissions/query_has_permission.rs +++ b/src/impls/chat/permissions/has_permission.rs @@ -2,14 +2,14 @@ use super::*; pub async fn handler( svc: &ChatServer, - request: Request, -) -> ServerResult> { + request: Request, +) -> ServerResult> { let user_id = svc.deps.auth(&request).await?; let request = request.into_message().await?; svc.deps .chat_tree - .query_has_permission_request(user_id, request) + .has_permission_request(user_id, request) .await .map(IntoResponse::into_response) } diff --git a/src/impls/chat/permissions/manage_user_roles.rs b/src/impls/chat/permissions/manage_user_roles.rs index aec1239..1599d9a 100644 --- a/src/impls/chat/permissions/manage_user_roles.rs +++ b/src/impls/chat/permissions/manage_user_roles.rs @@ -16,7 +16,9 @@ pub async fn handler( let chat_tree = &svc.deps.chat_tree; chat_tree.check_guild_user(guild_id, user_id).await?; - chat_tree.is_user_in_guild(guild_id, user_to_manage).await?; + chat_tree + .check_user_in_guild(guild_id, user_to_manage) + .await?; chat_tree .check_perms(guild_id, None, user_id, "roles.user.manage", false) .await?; @@ -30,16 +32,16 @@ pub async fn handler( .manage_user_roles_logic(guild_id, user_to_manage, give_role_ids, take_role_ids) .await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::UserRolesUpdated(stream_event::UserRolesUpdated { guild_id, user_id: user_to_manage, new_role_ids, }), - Some(PermCheck::new(guild_id, None, "roles.user.get", false)), + Some(PermCheck::new(guild_id, None, "roles.user.get")), EventContext::empty(), ); - Ok((ManageUserRolesResponse {}).into_response()) + Ok(ManageUserRolesResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/mod.rs b/src/impls/chat/permissions/mod.rs index 0b5c09f..c9866e5 100644 --- a/src/impls/chat/permissions/mod.rs +++ b/src/impls/chat/permissions/mod.rs @@ -7,8 +7,8 @@ pub mod get_permissions; pub mod get_user_roles; pub mod give_up_ownership; pub mod grant_ownership; +pub mod has_permission; pub mod manage_user_roles; pub mod modify_guild_role; pub mod move_role; -pub mod query_has_permission; pub mod set_permissions; diff --git a/src/impls/chat/permissions/modify_guild_role.rs b/src/impls/chat/permissions/modify_guild_role.rs index bdd138f..55fb608 100644 --- a/src/impls/chat/permissions/modify_guild_role.rs +++ b/src/impls/chat/permissions/modify_guild_role.rs @@ -26,7 +26,7 @@ pub async fn handler( let mut role = if let Some(raw) = chat_tree.get(key).await? { db::deser_role(raw) } else { - return Err(ServerError::NoSuchRole { guild_id, role_id }.into()); + bail!(ServerError::NoSuchRole { guild_id, role_id }); }; if let Some(new_name) = new_name.clone() { @@ -45,7 +45,7 @@ pub async fn handler( let ser_role = rkyv_ser(&role); chat_tree.insert(key, ser_role).await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::RoleUpdated(stream_event::RoleUpdated { guild_id, @@ -55,14 +55,9 @@ pub async fn handler( new_hoist, new_pingable, }), - Some(PermCheck::new( - guild_id, - None, - all_permissions::ROLES_GET, - false, - )), + Some(PermCheck::new(guild_id, None, all_permissions::ROLES_GET)), EventContext::empty(), ); - Ok((ModifyGuildRoleResponse {}).into_response()) + Ok(ModifyGuildRoleResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/move_role.rs b/src/impls/chat/permissions/move_role.rs index 0898cef..b4feb4e 100644 --- a/src/impls/chat/permissions/move_role.rs +++ b/src/impls/chat/permissions/move_role.rs @@ -24,22 +24,17 @@ pub async fn handler( chat_tree .move_role_logic(guild_id, role_id, Some(pos.clone())) .await?; - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::RoleMoved(stream_event::RoleMoved { guild_id, role_id, new_position: Some(pos), }), - Some(PermCheck::new( - guild_id, - None, - all_permissions::ROLES_GET, - false, - )), + Some(PermCheck::new(guild_id, None, all_permissions::ROLES_GET)), EventContext::empty(), ); } - Ok((MoveRoleResponse {}).into_response()) + Ok(MoveRoleResponse::new().into_response()) } diff --git a/src/impls/chat/permissions/set_permissions.rs b/src/impls/chat/permissions/set_permissions.rs index 7884c0d..f842718 100644 --- a/src/impls/chat/permissions/set_permissions.rs +++ b/src/impls/chat/permissions/set_permissions.rs @@ -47,7 +47,7 @@ pub async fn handler( } } for perm in &perms_to_give { - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::PermissionUpdated(stream_event::PermissionUpdated { guild_id, @@ -59,7 +59,7 @@ pub async fn handler( EventContext::new(for_users.clone()), ); } - svc.send_event_through_chan( + svc.broadcast( EventSub::Guild(guild_id), stream_event::Event::RolePermsUpdated(stream_event::RolePermissionsUpdated { guild_id, @@ -71,11 +71,10 @@ pub async fn handler( guild_id, None, all_permissions::ROLES_MANAGE, - false, )), EventContext::empty(), ); - Ok((SetPermissionsResponse {}).into_response()) + Ok(SetPermissionsResponse::new().into_response()) } else { Err(ServerError::NoPermissionsSpecified.into()) } diff --git a/src/impls/chat/private_channels/create_private_channel.rs b/src/impls/chat/private_channels/create_private_channel.rs new file mode 100644 index 0000000..cbb8d6e --- /dev/null +++ b/src/impls/chat/private_channels/create_private_channel.rs @@ -0,0 +1,121 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let CreatePrivateChannelRequest { + members: mut users_allowed, + is_dm, + name, + } = request.into_message().await?; + + users_allowed.dedup(); + + if users_allowed.contains(&user_id) { + bail!(( + "h.cant-allow-self", + "you cant add yourself to members for private channel, it is implied" + )); + } + + if is_dm && users_allowed.len() != 1 { + bail!(( + "h.must-have-one-member", + "private channels must have one member if they are direct message channels" + )); + } + + let maybe_dm_channel = if is_dm { + svc.deps + .chat_tree + .get(make_dm_with_user_key(user_id, users_allowed[0])) + .await? + .map(db::deser_id) + } else { + None + }; + + if let Some(channel_id) = maybe_dm_channel { + join_private_channel::logic(svc.deps.as_ref(), user_id, channel_id).await?; + + svc.broadcast( + EventSub::PrivateChannel(channel_id), + stream_event::Event::UserJoinedPrivateChannel(stream_event::UserJoinedPrivateChannel { + channel_id, + user_id, + }), + None, + EventContext::empty(), + ); + + svc.dispatch_private_channel_join(channel_id, user_id) + .await?; + + return Ok(CreatePrivateChannelResponse::new(channel_id).into_response()); + } + + let channel_id = logic(svc.deps.as_ref(), &users_allowed, user_id, is_dm, name).await?; + + svc.dispatch_private_channel_join(channel_id, user_id) + .await?; + + // create invite, using channel_id as invite_id + svc.deps + .chat_tree + .create_priv_invite_logic(channel_id, users_allowed.clone(), true) + .await?; + + // dispatch invites to users + for invitee_id in users_allowed { + svc.dispatch_user_invite_received( + user_id, + invitee_id, + pending_invite::Location::ChannelId(channel_id), + ) + .await?; + } + + Ok(CreatePrivateChannelResponse::new(channel_id).into_response()) +} + +pub async fn logic( + deps: &Dependencies, + users_allowed: &[u64], + creator_id: u64, + is_dm: bool, + name: Option, +) -> ServerResult { + let chat_tree = &deps.chat_tree; + + let channel_id = { + let mut rng = rand::rngs::SmallRng::from_entropy(); + let mut channel_id = rng.gen_range(1..u64::MAX); + while chat_tree.contains_key(&make_pc_key(channel_id)).await? { + channel_id = rng.gen_range(1..u64::MAX); + } + channel_id + }; + + let private_channel = PrivateChannel { + members: vec![creator_id], + is_dm, + name, + }; + let serialized = rkyv_ser(&private_channel); + + let mut batch = Batch::default(); + batch.insert(make_pc_creator_key(channel_id), creator_id.to_be_bytes()); + batch.insert(make_pc_key(channel_id), serialized); + if is_dm { + batch.insert( + make_dm_with_user_key(creator_id, users_allowed[0]), + channel_id.to_be_bytes(), + ); + } + chat_tree.apply_batch(batch).await?; + + Ok(channel_id) +} diff --git a/src/impls/chat/private_channels/delete_private_channel.rs b/src/impls/chat/private_channels/delete_private_channel.rs new file mode 100644 index 0000000..8d00d7f --- /dev/null +++ b/src/impls/chat/private_channels/delete_private_channel.rs @@ -0,0 +1,80 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let DeletePrivateChannelRequest { channel_id } = request.into_message().await?; + + svc.deps + .chat_tree + .check_private_channel_creator(channel_id, user_id) + .await?; + + let deleted_channel = logic(svc.deps.as_ref(), user_id, channel_id).await?; + + for member in deleted_channel.members { + svc.dispatch_private_channel_leave(channel_id, member) + .await?; + } + + broadcast!( + svc, + EventSub::PrivateChannel(channel_id), + PrivateChannelDeleted { channel_id } + ); + + Ok(DeletePrivateChannelResponse::new().into_response()) +} + +pub async fn logic( + deps: &Dependencies, + user_id: u64, + channel_id: u64, +) -> ServerResult { + let chat_tree = &deps.chat_tree; + + let creator_id_raw = chat_tree + .get(make_pc_creator_key(channel_id)) + .await? + .ok_or(ServerError::NoSuchPrivateChannel(channel_id))?; + let creator_id = deser_id(creator_id_raw); + + if user_id != creator_id { + bail!(( + "h.user-not-private-channel-creator", + "you must be the creator of the private channel to delete it" + )); + } + + let private_channel = get_private_channel::logic(deps, channel_id).await?; + + let batch = + db::create_batch_delete_prefix(&chat_tree.chat_tree, make_pc_key(channel_id)).await?; + + // also delete invite, if any + let mut batch = batch.merge( + db::create_batch_delete_prefix( + &chat_tree.chat_tree, + make_priv_invite_key(&channel_id.to_string()), + ) + .await?, + ); + + if private_channel.is_dm { + batch.remove(make_dm_with_user_key( + private_channel.members[0], + private_channel.members[1], + )); + batch.remove(make_dm_with_user_key( + private_channel.members[1], + private_channel.members[0], + )); + } + + chat_tree.apply_batch(batch).await?; + + Ok(private_channel) +} diff --git a/src/impls/chat/private_channels/get_private_channel.rs b/src/impls/chat/private_channels/get_private_channel.rs new file mode 100644 index 0000000..dbda552 --- /dev/null +++ b/src/impls/chat/private_channels/get_private_channel.rs @@ -0,0 +1,34 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let GetPrivateChannelRequest { channel_ids } = request.into_message().await?; + + for channel_id in channel_ids.iter().copied() { + svc.deps + .chat_tree + .check_private_channel_user(channel_id, user_id) + .await?; + } + + let mut channels = HashMap::with_capacity(channel_ids.len()); + for channel_id in channel_ids { + let channel = logic(svc.deps.as_ref(), channel_id).await?; + channels.insert(channel_id, channel); + } + + Ok(GetPrivateChannelResponse::new(channels).into_response()) +} + +pub async fn logic(deps: &Dependencies, channel_id: u64) -> ServerResult { + deps.chat_tree + .get(make_pc_key(channel_id)) + .await? + .ok_or(ServerError::NoSuchPrivateChannel(channel_id)) + .map(db::deser_private_channel) + .map_err(Into::into) +} diff --git a/src/impls/chat/private_channels/get_private_channel_list.rs b/src/impls/chat/private_channels/get_private_channel_list.rs new file mode 100644 index 0000000..035f78d --- /dev/null +++ b/src/impls/chat/private_channels/get_private_channel_list.rs @@ -0,0 +1,16 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let channels = svc + .deps + .chat_tree + .get_user_private_channels(user_id) + .await?; + + Ok(GetPrivateChannelListResponse::new(channels).into_response()) +} diff --git a/src/impls/chat/private_channels/join_private_channel.rs b/src/impls/chat/private_channels/join_private_channel.rs new file mode 100644 index 0000000..3ec4f81 --- /dev/null +++ b/src/impls/chat/private_channels/join_private_channel.rs @@ -0,0 +1,57 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let JoinPrivateChannelRequest { channel_id } = request.into_message().await?; + + let invite_id = channel_id.to_string(); + let channel_id = svc + .deps + .chat_tree + .use_priv_invite_logic(user_id, &invite_id) + .await?; + + logic(svc.deps.as_ref(), user_id, channel_id).await?; + + svc.broadcast( + EventSub::PrivateChannel(channel_id), + stream_event::Event::UserJoinedPrivateChannel(stream_event::UserJoinedPrivateChannel { + channel_id, + user_id, + }), + None, + EventContext::empty(), + ); + + svc.dispatch_private_channel_join(channel_id, user_id) + .await?; + + Ok(JoinPrivateChannelResponse::new().into_response()) +} + +pub async fn logic(deps: &Dependencies, user_id: u64, channel_id: u64) -> ServerResult<()> { + let mut private_channel = get_private_channel::logic(deps, channel_id).await?; + + if private_channel.members.contains(&user_id) { + bail!(("h.already-joined", "you are already in the private channel")); + } + + let other_user_id = private_channel.members[0]; + private_channel.members.push(user_id); + + let mut batch = Batch::default(); + batch.insert(make_pc_key(channel_id), rkyv_ser(&private_channel)); + if private_channel.is_dm { + batch.insert( + make_dm_with_user_key(user_id, other_user_id), + channel_id.to_be_bytes(), + ); + } + deps.chat_tree.apply_batch(batch).await?; + + Ok(()) +} diff --git a/src/impls/chat/private_channels/leave_private_channel.rs b/src/impls/chat/private_channels/leave_private_channel.rs new file mode 100644 index 0000000..47cfdec --- /dev/null +++ b/src/impls/chat/private_channels/leave_private_channel.rs @@ -0,0 +1,61 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let LeavePrivateChannelRequest { channel_id } = request.into_message().await?; + + svc.deps + .chat_tree + .check_private_channel_user(channel_id, user_id) + .await?; + + logic(svc.deps.as_ref(), user_id, channel_id).await?; + + svc.dispatch_private_channel_leave(channel_id, user_id) + .await?; + + broadcast!( + svc, + EventSub::PrivateChannel(channel_id), + UserLeftPrivateChannel { + channel_id, + user_id, + } + ); + + Ok(LeavePrivateChannelResponse::new().into_response()) +} + +pub async fn logic(deps: &Dependencies, user_id: u64, channel_id: u64) -> ServerResult<()> { + if deps + .chat_tree + .is_user_private_channel_creator(channel_id, user_id) + .await? + { + bail!(( + "h.cant-leave", + "private channel creators cant leave the channel, they must delete it" + )); + } + + let mut private_channel = get_private_channel::logic(deps, channel_id).await?; + + if let Some(pos) = private_channel.members.iter().position(|id| user_id.eq(id)) { + private_channel.members.remove(pos); + } else { + bail!(ServerError::UserNotInPrivateChannel { + channel_id, + user_id + }); + } + + deps.chat_tree + .insert(make_pc_key(channel_id), rkyv_ser(&private_channel)) + .await?; + + Ok(()) +} diff --git a/src/impls/chat/private_channels/mod.rs b/src/impls/chat/private_channels/mod.rs new file mode 100644 index 0000000..eccbea9 --- /dev/null +++ b/src/impls/chat/private_channels/mod.rs @@ -0,0 +1,11 @@ +use super::*; + +pub mod create_private_channel; +pub mod delete_private_channel; +pub mod get_private_channel; +pub mod get_private_channel_list; +pub mod join_private_channel; +pub mod leave_private_channel; +pub mod update_private_channel; +pub mod update_private_channel_members; +pub mod update_private_channel_name; diff --git a/src/impls/chat/private_channels/update_private_channel.rs b/src/impls/chat/private_channels/update_private_channel.rs new file mode 100644 index 0000000..c121e4a --- /dev/null +++ b/src/impls/chat/private_channels/update_private_channel.rs @@ -0,0 +1,65 @@ +use super::*; + +pub async fn handler( + svc: &ChatServer, + request: Request, +) -> ServerResult> { + let user_id = svc.deps.auth(&request).await?; + + let UpdatePrivateChannelRequest { + channel_id, + new_name, + added_members, + removed_members, + } = request.into_message().await?; + + svc.deps + .chat_tree + .check_private_channel_user(channel_id, user_id) + .await?; + + if let Some(new_name) = new_name { + update_private_channel_name::logic(svc.deps.as_ref(), channel_id, new_name.clone()).await?; + + broadcast!( + svc, + EventSub::PrivateChannel(channel_id), + PrivateChannelUpdated { + channel_id, + new_name: Some(new_name), + } + ); + } + + if added_members.is_empty().not() || removed_members.is_empty().not() { + let (added, removed) = + update_private_channel_members::logic(svc, channel_id, added_members, removed_members) + .await?; + + // dispatch invites to new members + for invitee_id in added { + svc.dispatch_user_invite_received( + user_id, + invitee_id, + pending_invite::Location::ChannelId(channel_id), + ) + .await?; + } + + // dispatch left events to removed users + for member in removed { + svc.dispatch_private_channel_leave(channel_id, member) + .await?; + broadcast!( + svc, + EventSub::PrivateChannel(channel_id), + UserLeftPrivateChannel { + channel_id, + user_id, + } + ); + } + } + + Ok(UpdatePrivateChannelResponse::new().into_response()) +} diff --git a/src/impls/chat/private_channels/update_private_channel_members.rs b/src/impls/chat/private_channels/update_private_channel_members.rs new file mode 100644 index 0000000..dba5dbf --- /dev/null +++ b/src/impls/chat/private_channels/update_private_channel_members.rs @@ -0,0 +1,65 @@ +use super::*; + +pub async fn logic( + svc: &ChatServer, + channel_id: u64, + added: Vec, + removed: Vec, +) -> ServerResult<(Vec, Vec)> { + let mut private_channel = get_private_channel::logic(svc.deps.as_ref(), channel_id).await?; + + if private_channel.is_dm { + bail!(( + "h.cant-update-members", + "private channel is a direct message channel, cant change members" + )); + } + + let members_before = private_channel.members.clone(); + + private_channel + .members + .retain(|id| removed.contains(id).not()); + private_channel.members.extend(added); + private_channel.members.dedup(); + + // members_before: [1, 2] + // private_channel.members: [1, 2, 3] + // members_added: [3] + let members_added = private_channel + .members + .iter() + .filter_map(|id| members_before.contains(id).not().then(|| *id)) + .collect::>(); + + // members_before: [1, 2, 3, 4] + // private_channel.members: [2, 3, 4] + // members_removed: [1] + let members_removed = members_before + .iter() + .filter_map(|id| private_channel.members.contains(id).not().then(|| *id)) + .collect::>(); + + // remove added members, they didnt join yet + private_channel + .members + .retain(|id| members_added.contains(id).not()); + + // insert new channel + let serialized = rkyv_ser(&private_channel); + svc.deps + .chat_tree + .insert(make_pc_key(channel_id), serialized) + .await?; + + // update invite allowed users + svc.deps + .chat_tree + .update_priv_invite_allowed_users(channel_id.to_string(), |allowed_users| { + allowed_users.retain(|id| members_removed.contains(id).not()); + allowed_users.extend(members_added.iter().copied()); + }) + .await?; + + Ok((members_added, members_removed)) +} diff --git a/src/impls/chat/private_channels/update_private_channel_name.rs b/src/impls/chat/private_channels/update_private_channel_name.rs new file mode 100644 index 0000000..e00f065 --- /dev/null +++ b/src/impls/chat/private_channels/update_private_channel_name.rs @@ -0,0 +1,12 @@ +use super::*; + +pub async fn logic(deps: &Dependencies, channel_id: u64, new_name: String) -> ServerResult<()> { + let mut private_channel = get_private_channel::logic(deps, channel_id).await?; + private_channel.name = new_name.is_empty().not().then(|| new_name); + + deps.chat_tree + .insert(make_pc_key(channel_id), rkyv_ser(&private_channel)) + .await?; + + Ok(()) +} diff --git a/src/impls/emote/add_emote_to_pack.rs b/src/impls/emote/add_emote_to_pack.rs index 4a0a122..00132ee 100644 --- a/src/impls/emote/add_emote_to_pack.rs +++ b/src/impls/emote/add_emote_to_pack.rs @@ -6,35 +6,39 @@ pub async fn handler( ) -> ServerResult> { let user_id = svc.deps.auth(&request).await?; - let AddEmoteToPackRequest { pack_id, emote } = request.into_message().await?; - - if let Some(emote) = emote { - svc.deps - .emote_tree - .check_if_emote_pack_owner(pack_id, user_id) - .await?; - - let emote_key = make_emote_pack_emote_key(pack_id, &emote.name); - let data = rkyv_ser(&emote); - - svc.deps.emote_tree.insert(emote_key, data).await?; - - let equipped_users = svc - .deps - .emote_tree - .calculate_users_pack_equipped(pack_id) - .await?; - svc.send_event_through_chan( - EventSub::Homeserver, - stream_event::Event::EmotePackEmotesUpdated(EmotePackEmotesUpdated { - pack_id, - added_emotes: vec![emote], - deleted_emotes: Vec::new(), - }), - None, - EventContext::new(equipped_users), - ); - } - - Ok((AddEmoteToPackResponse {}).into_response()) + let AddEmoteToPackRequest { + pack_id, + image_id, + name, + } = request.into_message().await?; + + svc.deps + .emote_tree + .check_if_emote_pack_owner(pack_id, user_id) + .await?; + + let emote = Emote::new(image_id, name); + + let emote_key = make_emote_pack_emote_key(pack_id, &emote.name); + let data = rkyv_ser(&emote); + + svc.deps.emote_tree.insert(emote_key, data).await?; + + let equipped_users = svc + .deps + .emote_tree + .calculate_users_pack_equipped(pack_id) + .await?; + svc.send_event_through_chan( + EventSub::Homeserver, + stream_event::Event::EmotePackEmotesUpdated(EmotePackEmotesUpdated { + pack_id, + added_emotes: vec![emote], + deleted_emotes: Vec::new(), + }), + None, + EventContext::new(equipped_users), + ); + + Ok(AddEmoteToPackResponse::new().into_response()) } diff --git a/src/impls/emote/delete_emote_from_pack.rs b/src/impls/emote/delete_emote_from_pack.rs index d7289a8..ce26851 100644 --- a/src/impls/emote/delete_emote_from_pack.rs +++ b/src/impls/emote/delete_emote_from_pack.rs @@ -33,5 +33,5 @@ pub async fn handler( EventContext::new(equipped_users), ); - Ok((DeleteEmoteFromPackResponse {}).into_response()) + Ok(DeleteEmoteFromPackResponse::new().into_response()) } diff --git a/src/impls/emote/delete_emote_pack.rs b/src/impls/emote/delete_emote_pack.rs index f4a3e06..c7213d7 100644 --- a/src/impls/emote/delete_emote_pack.rs +++ b/src/impls/emote/delete_emote_pack.rs @@ -45,5 +45,5 @@ pub async fn handler( EventContext::new(equipped_users), ); - Ok((DeleteEmotePackResponse {}).into_response()) + Ok(DeleteEmotePackResponse::new().into_response()) } diff --git a/src/impls/emote/dequip_emote_pack.rs b/src/impls/emote/dequip_emote_pack.rs index a9f0bbd..7b7276d 100644 --- a/src/impls/emote/dequip_emote_pack.rs +++ b/src/impls/emote/dequip_emote_pack.rs @@ -20,5 +20,5 @@ pub async fn handler( EventContext::new(vec![user_id]), ); - Ok((DequipEmotePackResponse {}).into_response()) + Ok(DequipEmotePackResponse::new().into_response()) } diff --git a/src/impls/emote/equip_emote_pack.rs b/src/impls/emote/equip_emote_pack.rs index 4bef94a..44daf1e 100644 --- a/src/impls/emote/equip_emote_pack.rs +++ b/src/impls/emote/equip_emote_pack.rs @@ -25,5 +25,5 @@ pub async fn handler( return Err(ServerError::EmotePackNotFound.into()); } - Ok((EquipEmotePackResponse {}).into_response()) + Ok(EquipEmotePackResponse::new().into_response()) } diff --git a/src/impls/emote/get_emote_pack_emotes.rs b/src/impls/emote/get_emote_pack_emotes.rs index e3921f7..4977e21 100644 --- a/src/impls/emote/get_emote_pack_emotes.rs +++ b/src/impls/emote/get_emote_pack_emotes.rs @@ -1,3 +1,7 @@ +use std::ops::Not; + +use harmony_rust_sdk::api::emote::get_emote_pack_emotes_response::EmotePackEmotes; + use super::*; pub async fn handler( @@ -6,27 +10,32 @@ pub async fn handler( ) -> ServerResult> { svc.deps.auth(&request).await?; - let GetEmotePackEmotesRequest { pack_id } = request.into_message().await?; + let GetEmotePackEmotesRequest { pack_id: pack_ids } = request.into_message().await?; + + let mut pack_emotes = HashMap::with_capacity(pack_ids.len()); + for pack_id in pack_ids { + let pack_key = make_emote_pack_key(pack_id); + + if svc.deps.emote_tree.contains_key(pack_key).await?.not() { + bail!(ServerError::EmotePackNotFound); + } - let pack_key = make_emote_pack_key(pack_id); + let emotes = svc + .deps + .emote_tree + .inner + .scan_prefix(&pack_key) + .await + .try_fold(Vec::new(), |mut all, res| { + let (key, value) = res.map_err(ServerError::from)?; + if key.len() > pack_key.len() { + all.push(db::deser_emote(value)); + } + ServerResult::Ok(all) + })?; - if svc.deps.emote_tree.get(pack_key).await?.is_none() { - return Err(ServerError::EmotePackNotFound.into()); + pack_emotes.insert(pack_id, EmotePackEmotes::new(emotes)); } - let emotes = svc - .deps - .emote_tree - .inner - .scan_prefix(&pack_key) - .await - .try_fold(Vec::new(), |mut all, res| { - let (key, value) = res.map_err(ServerError::from)?; - if key.len() > pack_key.len() { - all.push(db::deser_emote(value)); - } - ServerResult::Ok(all) - })?; - - Ok((GetEmotePackEmotesResponse { emotes }).into_response()) + Ok((GetEmotePackEmotesResponse::new(pack_emotes)).into_response()) } diff --git a/src/impls/media.rs b/src/impls/media.rs new file mode 100644 index 0000000..b27b2dc --- /dev/null +++ b/src/impls/media.rs @@ -0,0 +1,169 @@ +use std::{ + fs::Metadata, + path::{Path, PathBuf}, +}; + +use hrpc::exports::futures_util::{Stream, StreamExt}; +use prost::bytes::Bytes; +use smol_str::SmolStr; +use tokio::{ + fs::File, + io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, +}; + +use crate::{config::MediaConfig, utils::gen_rand_inline_str, ServerError}; + +const SEPERATOR: u8 = b'\n'; + +pub struct FileHandle { + pub file: BufReader, + pub metadata: Metadata, + pub name: String, + pub mime: String, + pub size: u64, + pub range: (u64, u64), +} + +impl FileHandle { + pub async fn read(mut self) -> Result, ServerError> { + let mut raw = Vec::with_capacity(self.size as usize); + self.file.read_to_end(&mut raw).await?; + Ok(raw) + } + + pub fn read_blocking(self) -> Result, ServerError> { + tokio::runtime::Handle::current().block_on(self.read()) + } +} + +pub struct MediaStore { + root: PathBuf, +} + +impl MediaStore { + pub fn new(config: &MediaConfig) -> Self { + Self { + root: config.media_root.clone(), + } + } + + pub async fn write_file( + &self, + mut chunks: impl Stream> + Unpin, + name: &str, + mime: &str, + ) -> Result { + let id = gen_rand_inline_str(); + let path = self.root.join(id.as_str()); + if path.exists() { + return Ok(id); + } + let first_chunk = chunks.next().await.ok_or(ServerError::MissingFiles)??; + + let file = tokio::fs::OpenOptions::default() + .append(true) + .create(true) + .open(path) + .await?; + let mut buf_writer = BufWriter::new(file); + + // Write prefix + buf_writer.write_all(name.as_bytes()).await?; + buf_writer.write_all(&[SEPERATOR]).await?; + buf_writer.write_all(mime.as_bytes()).await?; + buf_writer.write_all(&[SEPERATOR]).await?; + + // Write our first chunk + buf_writer.write_all(&first_chunk).await?; + + // flush before starting to write other chunks + buf_writer.flush().await?; + + while let Some(chunk) = chunks.next().await.transpose()? { + buf_writer.write_all(&chunk).await?; + } + + // flush everything else + buf_writer.flush().await?; + + Ok(id) + } + + pub async fn get_file(&self, id: &str) -> Result { + let is_jpeg = is_id_jpeg(id); + let (file, metadata, file_path) = self.get_file_handle(id).await?; + let (filename_raw, mimetype_raw, buf_file) = read_bufs(&file_path, file, is_jpeg).await?; + + let (start, end) = calculate_range(&filename_raw, &mimetype_raw, &metadata, is_jpeg); + + let mime = String::from_utf8_lossy(&mimetype_raw).into_owned(); + let name = String::from_utf8_lossy(&filename_raw).into_owned(); + + Ok(FileHandle { + file: buf_file, + metadata, + mime, + name, + size: end - start, + range: (start, end), + }) + } + + async fn get_file_handle(&self, id: &str) -> Result<(File, Metadata, PathBuf), ServerError> { + let file_path = self.root.join(id); + let file = tokio::fs::File::open(&file_path).await.map_err(|err| { + if let std::io::ErrorKind::NotFound = err.kind() { + ServerError::MediaNotFound + } else { + err.into() + } + })?; + let metadata = file.metadata().await?; + Ok((file, metadata, file_path)) + } +} + +pub async fn read_bufs<'file>( + path: &Path, + file: File, + is_jpeg: bool, +) -> Result<(Vec, Vec, BufReader), ServerError> { + if is_jpeg { + let mimetype = infer::get_from_path(path).ok().flatten().map_or_else( + || b"image/jpeg".to_vec(), + |t| t.mime_type().as_bytes().to_vec(), + ); + Ok((b"unknown".to_vec(), mimetype, BufReader::new(file))) + } else { + let mut buf_reader = BufReader::new(file); + + let mut filename_raw = Vec::with_capacity(20); + buf_reader.read_until(SEPERATOR, &mut filename_raw).await?; + filename_raw.pop(); + + let mut mimetype_raw = Vec::with_capacity(20); + buf_reader.read_until(SEPERATOR, &mut mimetype_raw).await?; + mimetype_raw.pop(); + + Ok((filename_raw, mimetype_raw, buf_reader)) + } +} + +pub fn calculate_range( + filename_raw: &[u8], + mimetype_raw: &[u8], + metadata: &Metadata, + is_jpeg: bool, +) -> (u64, u64) { + // + 2 is because we need to factor in the 2 b'\n' seperators + let start = is_jpeg + .then(|| 0) + .unwrap_or((filename_raw.len() + mimetype_raw.len()) as u64 + 2); + let end = metadata.len(); + + (start, end) +} + +pub fn is_id_jpeg(id: &str) -> bool { + id.ends_with("_jpeg") || id.ends_with("_jpegthumb") +} diff --git a/src/impls/mediaproxy/can_instant_view.rs b/src/impls/mediaproxy/can_instant_view.rs index c08492a..d8695b0 100644 --- a/src/impls/mediaproxy/can_instant_view.rs +++ b/src/impls/mediaproxy/can_instant_view.rs @@ -6,28 +6,14 @@ pub async fn handler( ) -> ServerResult> { svc.deps.auth(&request).await?; - let CanInstantViewRequest { url } = request.into_message().await?; + let CanInstantViewRequest { url: urls } = request.into_message().await?; - if let Some(val) = get_from_cache(&url) { - return Ok((CanInstantViewResponse { - can_instant_view: matches!(val.value, Metadata::Site(_)), - }) - .into_response()); + let mut can_instant_view = HashMap::with_capacity(urls.len()); + for url in urls { + let metadata = svc.fetch_metadata(url.clone()).await?; + let ok = matches!(metadata, Metadata::Site(_)); + can_instant_view.insert(url, ok); } - let response = svc - .http - .get(url) - .send() - .await - .map_err(ServerError::FailedToFetchLink)? - .error_for_status() - .map_err(ServerError::FailedToFetchLink)?; - - let ok = get_mimetype(response.headers()).eq("text/html"); - - Ok((CanInstantViewResponse { - can_instant_view: ok, - }) - .into_response()) + Ok(CanInstantViewResponse::new(can_instant_view).into_response()) } diff --git a/src/impls/mediaproxy/fetch_link_metadata.rs b/src/impls/mediaproxy/fetch_link_metadata.rs index 3aa3715..14a7136 100644 --- a/src/impls/mediaproxy/fetch_link_metadata.rs +++ b/src/impls/mediaproxy/fetch_link_metadata.rs @@ -6,9 +6,14 @@ pub async fn handler( ) -> ServerResult> { svc.deps.auth(&request).await?; - let FetchLinkMetadataRequest { url } = request.into_message().await?; + let FetchLinkMetadataRequest { url: urls } = request.into_message().await?; - let data = svc.fetch_metadata(url).await?.into(); + let mut data = HashMap::with_capacity(urls.len()); + for url in urls { + let metadata = svc.fetch_metadata(url.clone()).await?.into(); + data.insert(url, metadata); + } - Ok((FetchLinkMetadataResponse { data: Some(data) }).into_response()) + // TODO: return fetch errors + Ok(FetchLinkMetadataResponse::new(data, HashMap::new()).into_response()) } diff --git a/src/impls/mediaproxy/mod.rs b/src/impls/mediaproxy/mod.rs index 73a177b..6186653 100644 --- a/src/impls/mediaproxy/mod.rs +++ b/src/impls/mediaproxy/mod.rs @@ -1,10 +1,13 @@ -use crate::api::mediaproxy::{fetch_link_metadata_response::Data, *}; +use crate::api::mediaproxy::{ + fetch_link_metadata_response::{metadata::Data, Metadata as HarmonyMetadata}, + *, +}; use ahash::RandomState; use dashmap::{mapref::one::Ref, DashMap}; use reqwest::Client as HttpClient; use webpage::HTML; -use std::time::Instant; +use std::{ops::Not, time::Instant}; use super::{get_mimetype, http, prelude::*}; @@ -17,12 +20,15 @@ fn site_metadata_from_html(html: &HTML) -> SiteMetadata { page_title: html.title.clone().unwrap_or_default(), description: html.description.clone().unwrap_or_default(), url: html.url.clone().unwrap_or_default(), - image: html + thumbnail: html .opengraph .images - .last() - .map(|og| og.url.clone()) - .unwrap_or_default(), + .iter() + .map(|img| site_metadata::ThumbnailImage { + url: img.url.clone(), + ..Default::default() + }) + .collect(), ..Default::default() } } @@ -47,16 +53,42 @@ impl From for Data { size, } => Data::IsMedia(MediaMetadata { mimetype: mimetype.into(), - filename: filename.into(), + name: filename.into(), size, + ..Default::default() }), } } } +impl From for HarmonyMetadata { + fn from(metadata: Metadata) -> Self { + HarmonyMetadata::new(Data::from(metadata)) + } +} + +const DEFAULT_MAX_AGE: u64 = 30 * 60; + struct TimedCacheValue { value: T, since: Instant, + /// this corresponds to the `max-age` of `cache-control` header + max_age: u64, +} + +impl TimedCacheValue { + fn new(value: T, max_age: u64) -> Self { + Self { + value, + since: Instant::now(), + max_age, + } + } + + /// whether this value is stale (and should not be used) or not + fn is_stale(&self) -> bool { + self.since.elapsed().as_secs() >= self.max_age + } } // TODO: investigate possible optimization since the key will always be an URL? @@ -69,7 +101,7 @@ fn get_from_cache(url: &str) -> Option // Value is available, check if it is expired Some(val) => { // Remove value if it is expired - if val.since.elapsed().as_secs() >= 30 * 60 { + if val.is_stale() { drop(val); // explicit drop to tell we don't need it anymore CACHE.remove(url); None @@ -118,6 +150,29 @@ impl MediaproxyServer { .error_for_status() .map_err(ServerError::FailedToFetchLink)?; + let max_age = response + .headers() + .get(&http::header::CACHE_CONTROL) + .and_then(|header| { + let header_str = header.to_str().ok()?; + let parse_max_age = || { + header_str + .split(',') + .map(str::trim) + .find_map(|item| { + item.strip_prefix("max-age=") + .or_else(|| item.strip_prefix("s-maxage=")) + }) + .and_then(|raw| raw.parse::().ok()) + }; + header_str + .contains("no-store") + .not() + .then(parse_max_age) + .unwrap_or(Some(0)) + }) + .unwrap_or(DEFAULT_MAX_AGE); + let is_html = response .headers() .get(&http::header::CONTENT_TYPE) @@ -158,13 +213,7 @@ impl MediaproxyServer { }; // Insert to cache since successful - CACHE.insert( - raw_url, - TimedCacheValue { - value: metadata.clone(), - since: Instant::now(), - }, - ); + CACHE.insert(raw_url, TimedCacheValue::new(metadata.clone(), max_age)); Ok(metadata) } diff --git a/src/impls/mod.rs b/src/impls/mod.rs index ac5ee3b..a6951f1 100644 --- a/src/impls/mod.rs +++ b/src/impls/mod.rs @@ -1,21 +1,22 @@ pub mod admin_action; pub mod against; pub mod auth; -pub mod batch; pub mod chat; pub mod emote; +pub mod media; pub mod mediaproxy; pub mod profile; pub mod rest; pub mod sync; -#[cfg(feature = "voice")] -pub mod voice; +#[cfg(feature = "webrtc")] +pub mod webrtc; +use harmony_rust_sdk::api::chat::{stream_event, Event}; use prelude::*; use std::str::FromStr; -use crate::api::HomeserverIdentifier; +use crate::{api::HomeserverIdentifier, impls::chat::EventBroadcast}; use hyper::{http, Uri}; use lettre::{ message::{header, Mailbox, MultiPart, SinglePart}, @@ -29,12 +30,17 @@ use tokio::sync::{broadcast, mpsc}; use crate::{config::Config, key, SharedConfig, SharedConfigData}; use self::{ - auth::AuthTree, chat::ChatTree, emote::EmoteTree, profile::ProfileTree, rest::RestServiceLayer, + auth::AuthTree, + chat::{ChatTree, EventContext, EventSub, PermCheck}, + emote::EmoteTree, + media::MediaStore, + profile::ProfileTree, + rest::RestServiceLayer, sync::EventDispatch, }; pub mod prelude { - pub use std::{convert::TryInto, mem::size_of}; + pub use std::{collections::HashMap, convert::TryInto, mem::size_of}; pub use crate::{ db::{self, deser_id, rkyv_arch, rkyv_ser, Batch, Db, DbResult, Tree}, @@ -79,6 +85,7 @@ pub struct Dependencies { pub key_manager: Option>, pub http: HttpClient, pub email: Option>, + pub media: MediaStore, pub config: Config, pub runtime_config: SharedConfig, @@ -139,6 +146,7 @@ impl Dependencies { } builder.build() }), + media: MediaStore::new(&config.media), runtime_config: Arc::new(Mutex::new(SharedConfigData { motd: config.motd.clone(), @@ -148,6 +156,23 @@ impl Dependencies { Ok((Arc::new(this), fed_event_receiver)) } + + pub fn broadcast_chat( + &self, + sub: EventSub, + event: stream_event::Event, + perm_check: Option>, + context: EventContext, + ) { + let broadcast = EventBroadcast::new(sub, Event::Chat(event), perm_check, context); + + tracing::trace!( + "broadcasting events to {} receivers", + self.chat_event_sender.receiver_count() + ); + + drop(self.chat_event_sender.send(Arc::new(broadcast))); + } } pub fn setup_server( @@ -156,13 +181,11 @@ pub fn setup_server( log_level: tracing::Level, ) -> (impl MakeRoutes, RestServiceLayer) { use self::{ - auth::AuthServer, batch::BatchServer, chat::ChatServer, emote::EmoteServer, - mediaproxy::MediaproxyServer, profile::ProfileServer, sync::SyncServer, + auth::AuthServer, chat::ChatServer, emote::EmoteServer, mediaproxy::MediaproxyServer, + profile::ProfileServer, sync::SyncServer, }; use crate::api::{ - auth::auth_service_server::AuthServiceServer, - batch::batch_service_server::BatchServiceServer, - chat::chat_service_server::ChatServiceServer, + auth::auth_service_server::AuthServiceServer, chat::chat_service_server::ChatServiceServer, emote::emote_service_server::EmoteServiceServer, mediaproxy::media_proxy_service_server::MediaProxyServiceServer, profile::profile_service_server::ProfileServiceServer, @@ -176,29 +199,20 @@ pub fn setup_server( let chat_server = ChatServer::new(deps.clone()); let mediaproxy_server = MediaproxyServer::new(deps.clone()); let sync_server = SyncServer::new(deps.clone(), fed_event_receiver); - #[cfg(feature = "voice")] - let voice_server = self::voice::VoiceServer::new(deps.clone(), log_level); + #[cfg(feature = "webrtc")] + let webrtc_server = self::webrtc::WebRtcServer::new(deps.clone(), log_level); - let profile = ProfileServiceServer::new(profile_server.clone()); + let profile = ProfileServiceServer::new(profile_server); let emote = EmoteServiceServer::new(emote_server); let auth = AuthServiceServer::new(auth_server); - let chat = ChatServiceServer::new(chat_server.clone()); - let mediaproxy = MediaProxyServiceServer::new(mediaproxy_server.clone()); + let chat = ChatServiceServer::new(chat_server); + let mediaproxy = MediaProxyServiceServer::new(mediaproxy_server); let sync = PostboxServiceServer::new(sync_server); - #[cfg(feature = "voice")] - let voice = crate::api::voice::voice_service_server::VoiceServiceServer::new(voice_server); - - let batchable_services = { - let profile = ProfileServiceServer::new(profile_server.batch()); - let chat = ChatServiceServer::new(chat_server.batch()); - let mediaproxy = MediaProxyServiceServer::new(mediaproxy_server.batch()); - combine_services!(profile, chat, mediaproxy) - }; - - let rest = RestServiceLayer::new(deps.clone()); + #[cfg(feature = "webrtc")] + let webrtc = + crate::api::webrtc::web_rtc_service_server::WebRtcServiceServer::new(webrtc_server); - let batch_server = BatchServer::new(deps, batchable_services); - let batch = BatchServiceServer::new(batch_server); + let rest = RestServiceLayer::new(deps); let server = combine_services!( profile, @@ -207,9 +221,8 @@ pub fn setup_server( chat, mediaproxy, sync, - #[cfg(feature = "voice")] - voice, - batch + #[cfg(feature = "webrtc")] + webrtc ); (server, rest) @@ -226,7 +239,7 @@ pub async fn send_email( ) -> ServerResult<()> { let to = to .parse::() - .map_err(|err| ("h.invalid-email", format!("email is invalid: {}", err)))?; + .map_err(|err| ("h.invalid-email", format!("email is invalid: {err}")))?; let from = deps .config .email @@ -270,13 +283,11 @@ pub async fn send_email( .map_err(|err| err.to_string())?; if !response.is_positive() { + let code = response.code(); + let error = response.first_line().unwrap_or("unknown error"); bail!(( "scherzo.mailserver-error", - format!( - "failed to send email (code {}): {}", - response.code(), - response.first_line().unwrap_or("unknown error") - ) + format!("failed to send email (code {code}): {error}") )); } diff --git a/src/impls/profile/get_profile.rs b/src/impls/profile/get_profile.rs index d43f187..706962f 100644 --- a/src/impls/profile/get_profile.rs +++ b/src/impls/profile/get_profile.rs @@ -6,13 +6,13 @@ pub async fn handler( ) -> ServerResult> { svc.deps.auth(&request).await?; - let GetProfileRequest { user_id } = request.into_message().await?; + let GetProfileRequest { user_id: user_ids } = request.into_message().await?; - svc.deps - .profile_tree - .get_profile_logic(user_id) - .await - .map(|p| GetProfileResponse { profile: Some(p) }) - .map(IntoResponse::into_response) - .map_err(Into::into) + let mut profiles = HashMap::with_capacity(user_ids.len()); + for user_id in user_ids { + let profile = svc.deps.profile_tree.get_profile_logic(user_id).await?; + profiles.insert(user_id, profile); + } + + Ok(GetProfileResponse::new(profiles).into_response()) } diff --git a/src/impls/profile/mod.rs b/src/impls/profile/mod.rs index ea2d08b..6c87ff0 100644 --- a/src/impls/profile/mod.rs +++ b/src/impls/profile/mod.rs @@ -13,6 +13,7 @@ pub mod get_app_data; pub mod get_profile; pub mod set_app_data; pub mod update_profile; +pub mod update_status; #[derive(Clone)] pub struct ProfileServer { @@ -57,6 +58,8 @@ impl ProfileService for ProfileServer { set_app_data, SetAppDataRequest, SetAppDataResponse; #[rate(4, 5)] update_profile, UpdateProfileRequest, UpdateProfileResponse; + #[rate(4, 5)] + update_status, UpdateStatusRequest, UpdateStatusResponse; } } @@ -78,8 +81,7 @@ impl ProfileTree { user_id: u64, new_user_name: Option, new_user_avatar: Option, - new_user_status: Option, - new_is_bot: Option, + new_user_status: Option, ) -> ServerResult<()> { let key = make_user_profile_key(user_id); @@ -99,10 +101,7 @@ impl ProfileTree { } } if let Some(new_status) = new_user_status { - profile.user_status = new_status; - } - if let Some(new_is_bot) = new_is_bot { - profile.is_bot = new_is_bot; + profile.user_status = Some(new_status); } let buf = rkyv_ser(&profile); @@ -126,8 +125,8 @@ impl ProfileTree { pub async fn does_username_exist(&self, username: &str) -> ServerResult { for res in self.scan_prefix(USER_PREFIX).await { let (_, value) = res?; - let profile = db::rkyv_arch::(&value); - if profile.user_name == username { + let maybe_profile = rkyv::check_archived_root::(&value); + if maybe_profile.map_or(false, |profile| profile.user_name == username) { return Ok(true); } } diff --git a/src/impls/profile/set_app_data.rs b/src/impls/profile/set_app_data.rs index a661f10..44f5e7d 100644 --- a/src/impls/profile/set_app_data.rs +++ b/src/impls/profile/set_app_data.rs @@ -12,5 +12,5 @@ pub async fn handler( .insert(make_user_metadata_key(user_id, &app_id), app_data) .await?; - Ok((SetAppDataResponse {}).into_response()) + Ok(SetAppDataResponse::new().into_response()) } diff --git a/src/impls/profile/update_profile.rs b/src/impls/profile/update_profile.rs index 516aba4..d85b002 100644 --- a/src/impls/profile/update_profile.rs +++ b/src/impls/profile/update_profile.rs @@ -10,8 +10,6 @@ pub async fn handler( let UpdateProfileRequest { new_user_name, new_user_avatar, - new_user_status, - new_is_bot, } = request.into_message().await?; if let Some(username) = new_user_name.as_deref() { @@ -26,8 +24,7 @@ pub async fn handler( user_id, new_user_name.clone(), new_user_avatar.clone(), - new_user_status, - new_is_bot, + None, ) .await?; @@ -37,9 +34,6 @@ pub async fn handler( user_id, new_username: new_user_name, new_avatar: new_user_avatar, - new_status: new_user_status, - new_is_bot, - new_account_kind: None, }), None, EventContext::new( @@ -50,5 +44,5 @@ pub async fn handler( ), ); - Ok((UpdateProfileResponse {}).into_response()) + Ok(UpdateProfileResponse::new().into_response()) } diff --git a/src/impls/profile/update_status.rs b/src/impls/profile/update_status.rs new file mode 100644 index 0000000..fd689be --- /dev/null +++ b/src/impls/profile/update_status.rs @@ -0,0 +1,33 @@ +use super::*; + +pub async fn handler( + svc: &ProfileServer, + request: Request, +) -> ServerResult> { + #[allow(unused_variables)] + let user_id = svc.deps.auth(&request).await?; + + let UpdateStatusRequest { new_status } = request.into_message().await?; + + svc.deps + .profile_tree + .update_profile_logic(user_id, None, None, new_status.clone()) + .await?; + + svc.send_event_through_chan( + EventSub::Homeserver, + stream_event::Event::StatusUpdated(StatusUpdated { + user_id, + new_status, + }), + None, + EventContext::new( + svc.deps + .chat_tree + .calculate_users_seeing_user(user_id) + .await?, + ), + ); + + Ok(UpdateStatusResponse::new().into_response()) +} diff --git a/src/impls/rest/download.rs b/src/impls/rest/download.rs index c4a122a..33ce775 100644 --- a/src/impls/rest/download.rs +++ b/src/impls/rest/download.rs @@ -1,10 +1,19 @@ -use std::{convert::Infallible, ops::Not}; - -use hrpc::{exports::futures_util::future::BoxFuture, server::transport::http::HttpResponse}; +use std::{cmp, convert::Infallible, fs::Metadata, ops::Not}; + +use hrpc::{ + exports::futures_util::{ + future::{self, BoxFuture, Either}, + ready, stream, FutureExt, Stream, StreamExt, + }, + server::transport::http::HttpResponse, +}; +use prost::bytes::BytesMut; use reqwest::Client as HttpClient; +use tokio::io::AsyncSeekExt; +use tokio_util::io::poll_read_buf; use tower::Service; -use crate::rest_error_response; +use crate::{impls::media::FileHandle, rest_error_response}; use super::*; @@ -63,13 +72,12 @@ impl Service for DownloadService { _ => return Ok(ServerError::InvalidFileId.into_rest_http_response()), }; - let media_root = deps.config.media.media_root.as_path(); let http_client = &deps.http; let host = &deps.config.host; let (content_disposition, content_type, content_body, content_length) = match file_id { FileId::External(url) => { - info!("Serving external image from {}", url); + info!("Serving external image from {url}"); let filename = url.path().split('/').last().unwrap_or("unknown"); let disposition = unsafe { disposition_header(filename) }; let resp = match make_request(http_client, url.to_string()).await { @@ -107,21 +115,19 @@ impl Service for DownloadService { info!("Serving HMC from {}", hmc); if format!("{}:{}", hmc.server(), hmc.port()) == host.as_str() { info!("Serving local media with id {}", hmc.id()); - match get_file(media_root, hmc.id()).await { - Ok(data) => data, + match deps.media.get_file(hmc.id()).await { + Ok(handle) => file_handle_to_data(handle), Err(err) => return Ok(err.into_rest_http_response()), } } else { // Safety: this is always valid, since HMC is a valid URL let url = unsafe { - format!( - "https://{}:{}/_harmony/media/download/{}", - hmc.server(), - hmc.port(), - hmc.id() - ) - .parse() - .unwrap_unchecked() + let server = hmc.server(); + let port = hmc.port(); + let id = hmc.id(); + format!("https://{server}:{port}/_harmony/media/download/{id}") + .parse() + .unwrap_unchecked() }; let resp = match make_request(http_client, url).await { Ok(resp) => resp, @@ -148,9 +154,9 @@ impl Service for DownloadService { } } FileId::Id(id) => { - info!("Serving local media with id {}", id); - match get_file(media_root, &id).await { - Ok(data) => data, + info!("Serving local media with id {id}"); + match deps.media.get_file(&id).await { + Ok(handle) => file_handle_to_data(handle), Err(err) => return Ok(err.into_rest_http_response()), } } @@ -180,128 +186,26 @@ pub fn handler(deps: Arc) -> RateLimit { ) } -// Safety: the `name` argument MUST ONLY contain ASCII characters. -unsafe fn disposition_header(name: &str) -> HeaderValue { - HeaderValue::from_maybe_shared_unchecked(Bytes::from( - format!("inline; filename={}", name).into_bytes(), - )) -} - -pub async fn get_file_handle( - media_root: &Path, - id: &str, -) -> Result<(File, Metadata, PathBuf), ServerError> { - let file_path = media_root.join(id); - let file = tokio::fs::File::open(&file_path).await.map_err(|err| { - if let std::io::ErrorKind::NotFound = err.kind() { - ServerError::MediaNotFound - } else { - err.into() - } - })?; - let metadata = file.metadata().await?; - Ok((file, metadata, file_path)) -} - -pub async fn read_bufs<'file>( - path: &Path, - file: &'file mut File, - is_jpeg: bool, -) -> Result<(Vec, Vec, BufReader<&'file mut File>), ServerError> { - if is_jpeg { - let mimetype = infer::get_from_path(path).ok().flatten().map_or_else( - || b"image/jpeg".to_vec(), - |t| t.mime_type().as_bytes().to_vec(), - ); - Ok((b"unknown".to_vec(), mimetype, BufReader::new(file))) - } else { - let mut buf_reader = BufReader::new(file); - - let mut filename_raw = Vec::with_capacity(20); - buf_reader.read_until(SEPERATOR, &mut filename_raw).await?; - filename_raw.pop(); - - let mut mimetype_raw = Vec::with_capacity(20); - buf_reader.read_until(SEPERATOR, &mut mimetype_raw).await?; - mimetype_raw.pop(); - - Ok((filename_raw, mimetype_raw, buf_reader)) - } +fn file_handle_to_data(handle: FileHandle) -> (HeaderValue, HeaderValue, Body, HeaderValue) { + let stream = Body::wrap_stream(file_stream( + handle.file.into_inner(), + optimal_buf_size(&handle.metadata), + handle.range, + )); + let disposition = unsafe { disposition_header(&handle.name) }; + let mime = unsafe { string_header(handle.mime) }; + let size = unsafe { string_header(handle.size.to_string()) }; + (disposition, mime, stream, size) } -pub async fn get_file_full( - media_root: &Path, - id: &str, -) -> Result<(String, String, Vec, u64), ServerError> { - let is_jpeg = is_id_jpeg(id); - let (mut file, metadata, file_path) = get_file_handle(media_root, id).await?; - let (filename_raw, mimetype_raw, mut buf_reader) = - read_bufs(&file_path, &mut file, is_jpeg).await?; - - let (start, end) = calculate_range(&filename_raw, &mimetype_raw, &metadata, is_jpeg); - let size = end - start; - let mut file_raw = Vec::with_capacity(size as usize); - buf_reader.read_to_end(&mut file_raw).await?; - - unsafe { - Ok(( - String::from_utf8_unchecked(filename_raw), - String::from_utf8_unchecked(mimetype_raw), - file_raw, - size, - )) - } -} - -pub fn calculate_range( - filename_raw: &[u8], - mimetype_raw: &[u8], - metadata: &Metadata, - is_jpeg: bool, -) -> (u64, u64) { - // + 2 is because we need to factor in the 2 b'\n' seperators - let start = is_jpeg - .then(|| 0) - .unwrap_or((filename_raw.len() + mimetype_raw.len()) as u64 + 2); - let end = metadata.len(); - - (start, end) -} - -pub fn is_id_jpeg(id: &str) -> bool { - id.ends_with("_jpeg") || id.ends_with("_jpegthumb") +// SAFETY: the `name` argument MUST ONLY contain ASCII characters. +unsafe fn disposition_header(name: &str) -> HeaderValue { + string_header(format!("inline; filename={name}")) } -pub async fn get_file( - media_root: &Path, - id: &str, -) -> Result<(HeaderValue, HeaderValue, hyper::Body, HeaderValue), ServerError> { - let is_jpeg = is_id_jpeg(id); - let (mut file, metadata, file_path) = get_file_handle(media_root, id).await?; - let (filename_raw, mimetype_raw, _) = read_bufs(&file_path, &mut file, is_jpeg).await?; - - let (start, end) = calculate_range(&filename_raw, &mimetype_raw, &metadata, is_jpeg); - let mimetype: Bytes = mimetype_raw.into(); - - let disposition = unsafe { - let filename = std::str::from_utf8_unchecked(&filename_raw); - // Safety: filenames must be valid ASCII characters since we get them through only ASCII allowed structures [ref:ascii_filename_upload] - disposition_header(filename) - }; - // Safety: mimetypes must be valid ASCII chars since we get them through only ASCII allowed structures [ref:ascii_mimetype_upload] - let mimetype = unsafe { HeaderValue::from_maybe_shared_unchecked(mimetype) }; - - let buf_size = optimal_buf_size(&metadata); - Ok(( - disposition, - mimetype, - hyper::Body::wrap_stream(file_stream(file, buf_size, (start, end))), - unsafe { - HeaderValue::from_maybe_shared_unchecked(Bytes::from( - (end - start).to_string().into_bytes(), - )) - }, - )) +// SAFETY: the `string` argument MUST ONLY contain ASCII characters. +unsafe fn string_header(string: String) -> HeaderValue { + HeaderValue::from_maybe_shared_unchecked(Bytes::from(string.into_bytes())) } fn file_stream( @@ -312,8 +216,9 @@ fn file_stream( use std::io::SeekFrom; let seek = async move { - // We will always seek from a point that is non-zero - file.seek(SeekFrom::Start(start)).await?; + if start != 0 { + file.seek(SeekFrom::Start(start)).await?; + } Ok(file) }; @@ -335,7 +240,7 @@ fn file_stream( let n = match ready!(poll_read_buf(Pin::new(&mut f), cx, &mut buf)) { Ok(n) => n as u64, Err(err) => { - tracing::debug!("file read error: {}", err); + tracing::debug!("file read error: {err}"); return Poll::Ready(Some(Err(err))); } }; diff --git a/src/impls/rest/mod.rs b/src/impls/rest/mod.rs index faf9e58..529dde7 100644 --- a/src/impls/rest/mod.rs +++ b/src/impls/rest/mod.rs @@ -2,15 +2,12 @@ use crate::{http, utils::http_ratelimit::RateLimit}; use self::{about::AboutService, download::DownloadService, upload::UploadService}; -use super::{gen_rand_inline_str, get_content_length, prelude::*}; +use super::{get_content_length, prelude::*}; use std::{ borrow::Cow, - cmp, convert::Infallible, - fs::Metadata, future::Future, - path::{Path, PathBuf}, pin::Pin, str::FromStr, task::{Context, Poll}, @@ -20,25 +17,17 @@ use std::{ use crate::api::{ exports::{ hrpc::{ - exports::futures_util::{ - future::{self, BoxFuture, Either}, - ready, stream, FutureExt, Stream, StreamExt, - }, + exports::futures_util::{future::BoxFuture, FutureExt}, server::transport::http::{box_body, HttpRequest, HttpResponse}, }, - prost::bytes::{Bytes, BytesMut}, + prost::bytes::Bytes, }, rest::{extract_file_info_from_download_response, FileId}, }; use hrpc::common::future::Ready; -use http::{header, HeaderValue, Method, StatusCode, Uri}; +use http::{header, HeaderValue, Method, StatusCode}; use hyper::Body; use pin_project::pin_project; -use tokio::{ - fs::File, - io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter}, -}; -use tokio_util::io::poll_read_buf; use tower::{Layer, Service}; use tracing::info; @@ -46,8 +35,6 @@ pub mod about; pub mod download; pub mod upload; -const SEPERATOR: u8 = b'\n'; - type Out = Result; #[derive(Clone)] diff --git a/src/impls/rest/upload.rs b/src/impls/rest/upload.rs index 225a187..18d6ca0 100644 --- a/src/impls/rest/upload.rs +++ b/src/impls/rest/upload.rs @@ -1,6 +1,9 @@ use std::convert::Infallible; -use hrpc::{exports::futures_util::future::BoxFuture, server::transport::http::HttpResponse}; +use hrpc::{ + exports::futures_util::{future::BoxFuture, TryStreamExt}, + server::transport::http::HttpResponse, +}; use tower::Service; use crate::{impls::auth::get_token_from_header_map, rest_error_response}; @@ -72,18 +75,22 @@ impl Service for UploadService { match multipart.next_field().await { Ok(maybe_field) => match maybe_field { Some(field) => { - let id = - match write_file(deps.config.media.media_root.as_path(), field, None) - .await - { - Ok(id) => id, - Err(err) => return Ok(err.into_rest_http_response()), - }; + let name = field.file_name().unwrap_or("unknown").to_string(); + let mime = field + .content_type() + .map_or("application/octet-stream", |m| m.essence_str()) + .to_string(); + let chunks = field.map_err(ServerError::MultipartError); + + let id = match deps.media.write_file(chunks, &name, &mime).await { + Ok(id) => id, + Err(err) => return Ok(err.into_rest_http_response()), + }; Ok(http::Response::builder() .status(StatusCode::OK) .body(box_body(Body::from( - format!(r#"{{ "id": "{}" }}"#, id).into_bytes(), + format!(r#"{{ "id": "{id}" }}"#).into_bytes(), ))) .unwrap()) } @@ -96,53 +103,3 @@ impl Service for UploadService { Box::pin(fut) } } - -pub async fn write_file( - media_root: &Path, - mut part: multer::Field<'static>, - write_to: Option, -) -> Result { - let id = gen_rand_inline_str(); - let path = write_to.unwrap_or_else(|| media_root.join(id.as_str())); - if path.exists() { - return Ok(id); - } - let first_chunk = part.chunk().await?.ok_or(ServerError::MissingFiles)?; - - let file = tokio::fs::OpenOptions::default() - .append(true) - .create(true) - .open(path) - .await?; - let mut buf_writer = BufWriter::new(file); - - // [tag:ascii_filename_upload] - let name = part.file_name().unwrap_or("unknown"); - // [tag:ascii_mimetype_upload] - let content_type = part - .content_type() - .map(|m| m.essence_str()) - .or_else(|| infer::get(&first_chunk).map(|t| t.mime_type())) - .unwrap_or("application/octet-stream"); - - // Write prefix - buf_writer.write_all(name.as_bytes()).await?; - buf_writer.write_all(&[SEPERATOR]).await?; - buf_writer.write_all(content_type.as_bytes()).await?; - buf_writer.write_all(&[SEPERATOR]).await?; - - // Write our first chunk - buf_writer.write_all(&first_chunk).await?; - - // flush before starting to write other chunks - buf_writer.flush().await?; - - while let Some(chunk) = part.chunk().await? { - buf_writer.write_all(&chunk).await?; - } - - // flush everything else - buf_writer.flush().await?; - - Ok(id) -} diff --git a/src/impls/sync/mod.rs b/src/impls/sync/mod.rs index c59b535..de1a295 100644 --- a/src/impls/sync/mod.rs +++ b/src/impls/sync/mod.rs @@ -8,15 +8,23 @@ use crate::api::{ }; use ahash::RandomState; use dashmap::{mapref::one::RefMut, DashMap}; +use harmony_rust_sdk::api::chat::{ + pending_invite, stream_event as chat_stream_event, stream_event::Event as ChatEvent, + PendingInvite, +}; use hrpc::exports::futures_util::TryFutureExt; use hrpc::{client::transport::http::Hyper, encode::encode_protobuf_message}; use hyper::{http::HeaderValue, Uri}; use tokio::sync::mpsc::UnboundedReceiver; -use tracing::error; +use tracing::{error, Instrument}; use crate::key::{self, Manager as KeyManager}; -use super::{http, prelude::*}; +use super::{ + chat::{EventContext, EventSub}, + http, + prelude::*, +}; use db::sync::*; pub mod notify_new_id; @@ -28,9 +36,14 @@ pub struct EventDispatch { pub event: Event, } -struct Clients(DashMap, RandomState>); +#[derive(Clone)] +pub struct Clients(Arc, RandomState>>); impl Clients { + fn new() -> Self { + Self(Arc::new(DashMap::default())) + } + fn get_client( &self, host: SmolStr, @@ -50,110 +63,149 @@ pub struct SyncServer { } impl SyncServer { - pub fn new(deps: Arc, mut dispatch_rx: UnboundedReceiver) -> Self { - let sync = Self { deps }; - let sync2 = sync.clone(); - let clients = Clients(DashMap::default()); - - tokio::spawn(async move { - let span = tracing::info_span!("federation_sync_task"); - let _guard = span.enter(); - loop { - tokio::select! { - _ = async { - let hosts = sync2.deps.sync_tree.scan_prefix(HOST_PREFIX).await.flat_map(|res| { - let key = match res { - Ok((key, _)) => key, - Err(err) => { - let err = ServerError::DbError(err); - error!("error occured while getting hosts for sync: {}", err); - return None; - } - }; - let (_, host_raw) = key.split_at(HOST_PREFIX.len()); - let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; - Some(SmolStr::new(host)) - }); - - for host in hosts { - if sync2.is_host_allowed(&host).is_ok() { - let mut client = clients.get_client(host.clone()); - if let Ok(queue) = sync2 - .generate_request(PullRequest {}) - .map_err(|_| ()) - .and_then(|req| { - client.pull(req).map_err(|_| ()) - }).and_then(|resp| { - resp.into_message().map_err(|_| ()) - }) - .await - { - for event in queue.event_queue { - if let Err(err) = sync2.push_logic(&host, event).await { - error!("error while executing sync event: {}", err); - } - } - } + // TODO: actually print errors here + pub async fn pull_events(&self, clients: &Clients) { + let hosts = self + .deps + .sync_tree + .scan_prefix(HOST_PREFIX) + .await + .flat_map(|res| { + let key = match res { + Ok((key, _)) => key, + Err(err) => { + let err = ServerError::DbError(err); + error!("error occured while getting hosts for sync: {err}"); + return None; + } + }; + let (_, host_raw) = key.split_at(HOST_PREFIX.len()); + let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; + Some(SmolStr::new(host)) + }); + + for host in hosts { + if self.is_host_allowed(&host).is_ok() { + tracing::debug!("pulling from host {host}"); + let mut client = clients.get_client(host.clone()); + if let Ok(queue) = self + .generate_request(PullRequest {}) + .map_err(|_| ()) + .and_then(|req| client.pull(req).map_err(|_| ())) + .and_then(|resp| resp.into_message().map_err(|_| ())) + .await + { + for event in queue.event_queue { + if let Err(err) = self.push_logic(&host, event).await { + error!("error while executing sync event: {err}"); + } + } + } + } + } + } + + // TODO: actually print errors here + pub async fn push_events( + &self, + clients: &Clients, + dispatch_rx: &mut UnboundedReceiver, + ) { + while let Some(EventDispatch { host, event }) = dispatch_rx.recv().await { + if self.is_host_allowed(&host).is_ok() { + match self.get_event_queue_raw(&host).await { + Ok(raw_queue) => { + let maybe_arch_queue = raw_queue + .as_ref() + .map(|raw_queue| rkyv_arch::(raw_queue)); + if !maybe_arch_queue.map_or(false, |v| v.event_queue.is_empty()) { + let queue = maybe_arch_queue.map_or_else(PullResponse::default, |v| { + v.deserialize(&mut rkyv::Infallible).unwrap() + }); + if let Err(err) = self.push_to_event_queue(&host, queue, event).await { + error!("error while pushing to event queue: {err}"); } + continue; } - tokio::time::sleep(Duration::from_secs(60)).await; - } => {} - _ = async { - while let Some(EventDispatch { host, event }) = dispatch_rx.recv().await { - if sync2.is_host_allowed(&host).is_ok() { - match sync2.get_event_queue_raw(&host).await { - Ok(raw_queue) => { - let maybe_arch_queue = raw_queue.as_ref().map(|raw_queue| rkyv_arch::(raw_queue)); - if !maybe_arch_queue.map_or(false, |v| v.event_queue.is_empty()) { - let queue = maybe_arch_queue.map_or_else( - PullResponse::default, - |v| v.deserialize(&mut rkyv::Infallible).unwrap() - ); - if let Err(err) = sync2.push_to_event_queue(&host, queue, event).await { - error!("error while pushing to event queue: {}", err); - } - continue; - } - - let mut client = clients.get_client(host.clone()); - let mut push_result = sync2 - .generate_request(PushRequest { event: Some(event.clone()) }) - .map_err(|_| ()) - .and_then(|req| { - client.push(req).map_err(|_| ()) - }) - .await; - let mut try_count = 0; - while try_count < 5 && push_result.is_err() { - push_result = sync2 - .generate_request(PushRequest { event: Some(event.clone()) }) - .map_err(|_| ()) - .and_then(|req| { - client.push(req).map_err(|_| ()) - }) - .await; - try_count += 1; - } - - if push_result.is_err() { - let queue = maybe_arch_queue.map_or_else( - PullResponse::default, - |v| v.deserialize(&mut rkyv::Infallible).unwrap() - ); - if let Err(err) = sync2.push_to_event_queue(&host, queue, event).await { - error!("error while pushing to event queue: {}", err); - } - } - } - Err(err) => error!("error occured while getting event queue: {}", err), - } + let mut client = clients.get_client(host.clone()); + let mut push_result = self + .generate_request(PushRequest { + event: Some(event.clone()), + }) + .map_err(|_| ()) + .and_then(|req| client.push(req).map_err(|_| ())) + .await; + let mut try_count = 0; + while try_count < 5 && push_result.is_err() { + push_result = self + .generate_request(PushRequest { + event: Some(event.clone()), + }) + .map_err(|_| ()) + .and_then(|req| client.push(req).map_err(|_| ())) + .await; + try_count += 1; + } + + if push_result.is_err() { + let queue = maybe_arch_queue.map_or_else(PullResponse::default, |v| { + v.deserialize(&mut rkyv::Infallible).unwrap() + }); + if let Err(err) = self.push_to_event_queue(&host, queue, event).await { + error!("error while pushing to event queue: {err}"); } } - } => {} + } + Err(err) => error!("error occured while getting event queue: {err}"), } } - }); + } + } + + pub fn new(deps: Arc, mut dispatch_rx: UnboundedReceiver) -> Self { + let sync = Self { deps }; + let clients = Clients::new(); + + let (initial_pull_tx, initial_pull_rx) = tokio::sync::oneshot::channel(); + + // TODO: it should probably be made so that when a pull fails for a host, + // we shouldn't try to push anymore events to it until it's pull succeeds again + + tokio::task::Builder::new() + .name("federation_pull_task") + .spawn({ + let clients = clients.clone(); + let sync = sync.clone(); + let fut = async move { + tracing::info!("started task"); + sync.pull_events(&clients).await; + initial_pull_tx + .send(()) + .expect("failed to send initial pull complete notification"); + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + sync.pull_events(&clients).await; + } + }; + fut.instrument(tracing::info_span!("federation_pull_task")) + }); + + tokio::task::Builder::new() + .name("federation_push_task") + .spawn({ + let sync = sync.clone(); + let fut = async move { + tracing::info!("started task"); + initial_pull_rx + .await + .expect("failed to get initial pull complete notification"); + loop { + sync.push_events(&clients, &mut dispatch_rx).await; + } + }; + fut.instrument(tracing::info_span!("federation_push_task")) + }); sync } @@ -237,22 +289,159 @@ impl SyncServer { } async fn push_logic(&self, host: &str, event: Event) -> ServerResult<()> { - if let Some(kind) = event.kind { - match kind { - Kind::UserRemovedFromGuild(UserRemovedFromGuild { user_id, guild_id }) => { - self.deps - .chat_tree - .remove_guild_from_guild_list(user_id, guild_id, host) - .await?; - } - Kind::UserAddedToGuild(UserAddedToGuild { user_id, guild_id }) => { - self.deps - .chat_tree - .add_guild_to_guild_list(user_id, guild_id, host) - .await?; + let chat_tree = &self.deps.chat_tree; + let Some(kind) = event.kind else { return Ok(()) }; + match kind { + Kind::UserRemovedFromGuild(UserRemovedFromGuild { user_id, guild_id }) => { + chat_tree + .remove_guild_from_guild_list(user_id, guild_id, host) + .await?; + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::GuildRemovedFromList(chat_stream_event::GuildRemovedFromList { + guild_id, + server_id: Some(host.to_string()), + }), + None, + EventContext::new(vec![user_id]), + ); + } + Kind::UserAddedToGuild(UserAddedToGuild { user_id, guild_id }) => { + chat_tree + .add_guild_to_guild_list(user_id, guild_id, host) + .await?; + + // TODO: figure out how to remove pending invites for guilds + + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::GuildAddedToList(chat_stream_event::GuildAddedToList { + guild_id, + server_id: Some(host.to_string()), + }), + None, + EventContext::new(vec![user_id]), + ); + } + Kind::UserInvited(UserInvited { + inviter_id, + location, + user_id, + }) => { + // TODO: add checks for location, user_id and inviter + let location = match location.ok_or("location can't be empty")? { + user_invited::Location::GuildInviteId(invite) => { + pending_invite::Location::GuildInviteId(invite) + } + user_invited::Location::ChannelId(channel_id) => { + pending_invite::Location::ChannelId(channel_id) + } + }; + + let invite = PendingInvite { + inviter_id, + location: Some(location.clone()), + server_id: Some(host.to_string()), + }; + + chat_tree.add_user_pending_invite(user_id, invite).await?; + + let location = match location { + pending_invite::Location::ChannelId(channel_id) => { + chat_stream_event::invite_received::Location::ChannelId(channel_id) + } + pending_invite::Location::GuildInviteId(invite_id) => { + chat_stream_event::invite_received::Location::GuildInviteId(invite_id) + } + }; + + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::InviteReceived(chat_stream_event::InviteReceived { + inviter_id, + location: Some(location), + server_id: Some(host.to_string()), + }), + None, + EventContext::new(vec![user_id]), + ); + } + Kind::UserRejectedInvite(UserRejectedInvite { + location, + user_id, + inviter_id, + }) => { + // TODO: add checks for location, user_id and inviter + let location = match location.ok_or("location can't be empty")? { + user_rejected_invite::Location::GuildInviteId(invite) => { + chat_stream_event::invite_rejected::Location::GuildInviteId(invite) + } + user_rejected_invite::Location::ChannelId(channel_id) => { + chat_stream_event::invite_rejected::Location::ChannelId(channel_id) + } + }; + + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::InviteRejected(chat_stream_event::InviteRejected { + invitee_id: user_id, + location: Some(location), + server_id: Some(host.to_string()), + }), + None, + EventContext::new(vec![inviter_id]), + ); + } + Kind::UserRemovedFromChannel(UserRemovedFromChannel { + user_id, + channel_id, + }) => { + // TODO: do we need to send events here? probably? is the user_id foreign or not??? + chat_tree + .remove_pc_from_pc_list(user_id, channel_id, host) + .await?; + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::PrivateChannelRemovedFromList( + chat_stream_event::PrivateChannelRemovedFromList { + channel_id, + server_id: Some(host.to_string()), + }, + ), + None, + EventContext::new(vec![user_id]), + ); + } + Kind::UserAddedToChannel(UserAddedToChannel { + user_id, + channel_id, + }) => { + chat_tree + .add_pc_to_pc_list(user_id, channel_id, host) + .await?; + + let location = pending_invite::Location::ChannelId(channel_id); + let res = chat_tree + .remove_user_pending_invite(user_id, Some(host), &location) + .await; + if res.as_ref().map_or_else( + |err| err.identifier == "h.no-such-pending-invite", + |_| false, + ) { + res?; } - Kind::UserInvited(_) => todo!(), - Kind::UserRejectedInvite(_) => todo!(), + + self.deps.broadcast_chat( + EventSub::Homeserver, + ChatEvent::PrivateChannelAddedToList( + chat_stream_event::PrivateChannelAddedToList { + channel_id, + server_id: Some(host.to_string()), + }, + ), + None, + EventContext::new(vec![user_id]), + ); } } Ok(()) diff --git a/src/impls/sync/push.rs b/src/impls/sync/push.rs index 815760c..083211f 100644 --- a/src/impls/sync/push.rs +++ b/src/impls/sync/push.rs @@ -22,5 +22,5 @@ pub async fn handler( if let Some(event) = request.into_message().await?.event { svc.push_logic(&host, event).await?; } - Ok((PushResponse {}).into_response()) + Ok(PushResponse::new().into_response()) } diff --git a/src/impls/voice/mod.rs b/src/impls/webrtc/mod.rs similarity index 96% rename from src/impls/voice/mod.rs rename to src/impls/webrtc/mod.rs index 631814b..0278f82 100644 --- a/src/impls/voice/mod.rs +++ b/src/impls/webrtc/mod.rs @@ -7,15 +7,7 @@ use super::prelude::*; use crate::api::{ exports::hrpc::bail_result, - voice::{ - stream_message_request::{Initialize, Message as RequestMessage}, - stream_message_response::{ - Initialized, JoinedChannel, Message as ResponseMessage, PreparedForJoinChannel, - UserJoined, UserLeft, - }, - voice_service_server::VoiceService, - *, - }, + webrtc::{web_rtc_service_server::WebRtcService, *}, }; use ahash::RandomState; use dashmap::DashMap; @@ -51,14 +43,14 @@ enum Event { } #[derive(Clone)] -pub struct VoiceServer { +pub struct WebRtcServer { worker_pool: WorkerPool, channels: Channels, disable_ratelimits: bool, deps: Arc, } -impl VoiceServer { +impl WebRtcServer { pub fn new(deps: Arc, log_level: Level) -> Self { Self { worker_pool: WorkerPool::new(log_level), @@ -69,7 +61,7 @@ impl VoiceServer { } } -impl VoiceService for VoiceServer { +impl WebRtcService for WebRtcServer { impl_ws_handlers! { #[rate(1, 10)] stream_message, StreamMessageRequest, StreamMessageResponse; diff --git a/src/impls/voice/stream_message.rs b/src/impls/webrtc/stream_message.rs similarity index 98% rename from src/impls/voice/stream_message.rs rename to src/impls/webrtc/stream_message.rs index 7921403..6ab1e7a 100644 --- a/src/impls/voice/stream_message.rs +++ b/src/impls/webrtc/stream_message.rs @@ -242,10 +242,7 @@ pub async fn handler( Event::UserLeft(user_left) => ResponseMessage::UserLeft(user_left), }; - bail_result!( - tx.send_message(StreamMessageResponse::new(Some(message))) - .await - ); + bail_result!(tx.send_message(StreamMessageResponse::new(message)).await); } ServerResult::Ok(()) }; diff --git a/src/lib.rs b/src/lib.rs index 629282f..eca3cc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,10 @@ once_cell, let_else, const_intrinsic_copy, - const_ptr_offset, const_mut_refs, - type_alias_impl_trait + type_alias_impl_trait, + drain_filter, + is_some_with )] #![allow(clippy::unit_arg, clippy::blocks_in_if_conditions)] diff --git a/src/main.rs b/src/main.rs index 229a670..6cdb56c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,14 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; use std::{ + any::Any, net::SocketAddr, path::{Path, PathBuf}, time::Duration, }; use harmony_rust_sdk::api::{ - chat::{content, guild_kind, ChannelKind, FormattedText, Permission}, + chat::{ChannelKind, Permission}, exports::hrpc::server::transport::{http::Hyper, Transport}, }; use hrpc::{ @@ -24,13 +25,15 @@ use hrpc::{ MakeRoutes, }, }; -use hyper::header; +use hyper::{header, StatusCode}; use scherzo::{ + api::chat::Content, config::Config, db::{ migration::{apply_migrations, get_db_version}, Db, }, + error::hrpc_error_response, impls::{ admin_action, against, chat::{AdminGuildKeys, DEFAULT_ROLE_ID}, @@ -41,6 +44,7 @@ use scherzo::{ }; use tower::limit::ConcurrencyLimitLayer; use tower_http::{ + catch_panic::CatchPanicLayer, cors::CorsLayer, map_response_body::MapResponseBodyLayer, sensitive_headers::SetSensitiveRequestHeadersLayer, @@ -178,6 +182,7 @@ async fn setup_db(db_path: String, config: &Config) -> (Db, usize) { let (current_db_version, needs_migration) = get_db_version(&db) .await .expect("something went wrong while checking if the db needs migrations!!!"); + info!("db version is {}", current_db_version); if needs_migration { // Backup db before attempting to apply migrations if current_db_version > 0 { @@ -228,6 +233,25 @@ fn setup_transport( .expect("failed to create transport") .layer(cors) .layer(MapResponseBodyLayer::new(box_body)) + .layer(CatchPanicLayer::custom(|err: Box| { + let err = err + .downcast_ref::() + .map(String::clone) + .or_else(|| err.downcast_ref::<&str>().map(|s| s.to_string())); + + if let Some(err) = &err { + tracing::error!("service panicked: {}", err); + } else { + tracing::error!("service panicked but unable to extract error message"); + } + + hrpc_error_response( + hrpc::proto::Error::new_internal_server_error( + err.unwrap_or_else(|| "unknown".to_string()), + ), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })) .layer(concurrency_limiter) .layer(SetSensitiveRequestHeadersLayer::new([ header::AUTHORIZATION, @@ -246,11 +270,24 @@ fn setup_transport( .layer(rest) .layer(against::AgainstLayer); + let mut enabled_tls = false; if let Some(tls_config) = deps.config.tls.as_ref() { - transport = transport - .configure_tls_files(tls_config.cert_file.clone(), tls_config.key_file.clone()); + if tls_config.cert_file.exists() && tls_config.key_file.exists() { + transport = transport + .configure_tls_files(tls_config.cert_file.clone(), tls_config.key_file.clone()); + enabled_tls = true; + } else { + error!("certificate file and key file specified, but the files don't exist"); + warn!("not enabling TLS"); + } } + info!( + "serving on {}://{}", + enabled_tls.then(|| "https").unwrap_or("http"), + addr + ); + transport.configure_hyper( HttpConfig::new() .http1_keep_alive(true) @@ -324,13 +361,7 @@ fn setup_tracing(console: bool, jaeger: bool, level_filter: Level) { async fn setup_admin_guild(deps: &Dependencies) { let guild_id = deps .chat_tree - .create_guild_logic( - 0, - "Admin".to_string(), - None, - None, - guild_kind::Kind::new_normal(guild_kind::Normal::new()), - ) + .create_guild_logic(0, "Admin".to_string(), None, None) .await .unwrap(); deps.chat_tree @@ -364,14 +395,9 @@ async fn setup_admin_guild(deps: &Dependencies) { .unwrap(); deps.chat_tree .send_with_system( - guild_id, + Some(guild_id), cmd_id, - content::Content::TextMessage(content::TextContent { - content: Some(FormattedText::new( - admin_action::HELP_TEXT.to_string(), - Vec::new(), - )), - }), + Content::default().with_text(admin_action::HELP_TEXT), ) .await .unwrap(); diff --git a/src/utils/evec.rs b/src/utils/evec.rs index 2e4c7e7..d2d8219 100644 --- a/src/utils/evec.rs +++ b/src/utils/evec.rs @@ -6,7 +6,7 @@ use sled::IVec; #[derive(Debug, Clone)] pub enum EVec { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] Inline(IVec), Owned(AlignedVec), } @@ -20,7 +20,7 @@ impl Default for EVec { impl From for AlignedVec { fn from(evec: EVec) -> Self { match evec { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] EVec::Inline(inline) => { let mut vec = AlignedVec::with_capacity(inline.len()); vec.extend_from_slice(inline.as_ref()); @@ -34,7 +34,7 @@ impl From for AlignedVec { impl From for Vec { fn from(evec: EVec) -> Self { match evec { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] EVec::Inline(inline) => inline.to_vec(), EVec::Owned(owned) => owned.into_vec(), } @@ -50,7 +50,8 @@ impl From for EVec { #[cfg(feature = "sled")] impl From for EVec { fn from(ivec: IVec) -> Self { - let vec: AlignedVec = EVec::Inline(ivec).into(); + let mut vec = AlignedVec::with_capacity(ivec.len()); + vec.extend_from_slice(ivec.as_ref()); EVec::Owned(vec) } } @@ -59,6 +60,7 @@ impl From for EVec { impl From for IVec { fn from(evec: EVec) -> Self { match evec { + #[cfg(feature = "sledaa")] EVec::Inline(ivec) => ivec, EVec::Owned(vec) => vec.into_vec().into(), } @@ -98,7 +100,7 @@ impl From<[u8; N]> for EVec { impl AsRef<[u8]> for EVec { fn as_ref(&self) -> &[u8] { match self { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] EVec::Inline(inline) => inline.as_ref(), EVec::Owned(owned) => owned.as_slice(), } @@ -108,7 +110,7 @@ impl AsRef<[u8]> for EVec { impl AsMut<[u8]> for EVec { fn as_mut(&mut self) -> &mut [u8] { match self { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] EVec::Inline(inline) => inline.as_mut(), EVec::Owned(owned) => owned.as_mut_slice(), } @@ -146,7 +148,7 @@ impl TryFrom for [u8; N] { fn try_from(v: EVec) -> Result<[u8; N], Self::Error> { match v { - #[cfg(feature = "sled")] + #[cfg(feature = "sledaa")] EVec::Inline(ivec) => ivec.as_ref().try_into().map_err(|_| EVec::Inline(ivec)), EVec::Owned(vec) => vec.as_ref().try_into().map_err(|_| EVec::Owned(vec)), } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 064c160..781505a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,6 +4,8 @@ pub mod http_ratelimit; pub mod ratelimit; pub mod test; +use std::future::Future; + use hrpc::exports::{bytes::Bytes, http}; use hyper::HeaderMap; use rand::Rng; @@ -75,3 +77,42 @@ pub fn get_content_length(headers: &HeaderMap) -> http::HeaderValue { http::HeaderValue::from_maybe_shared_unchecked(Bytes::from_static(b"0")) }) } + +pub fn opt_fut(fut: Option) -> impl Future> +where + FutIn: Future, +{ + async { + match fut { + Some(fut) => Some(fut.await), + None => None, + } + } +} + +/// create an event. shorthand for oneof variant +/// +/// ```no_compile +/// event!(PrivateChannelUpdated { channel_id, new_name }); +/// ``` +macro_rules! event { + ($t:ident $fields:tt) => {{ + stream_event::Event::$t(stream_event::$t $fields) + }}; +} + +pub(crate) use event; + +macro_rules! broadcast { + ($svc:ident, $sub:expr, $event:ident $fields:tt) => { + $crate::utils::broadcast!($svc, $sub, None, $event $fields) + }; + ($svc:ident, $sub:expr, $perm:expr, $event:ident $fields:tt) => { + $crate::utils::broadcast!($svc, $sub, $perm, EventContext::empty(), $event $fields) + }; + ($svc:ident, $sub:expr, $perm:expr, $ctx:expr, $event:ident $fields:tt) => { + $svc.broadcast($sub, $crate::utils::event!($event $fields), $perm, $ctx) + }; +} + +pub(crate) use broadcast;