diff --git a/.rustfmt.toml b/.rustfmt.toml index 80c01a69b..dfeeb3bfd 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -3,3 +3,5 @@ edition = "2021" imports_layout = "HorizontalVertical" imports_granularity = "Crate" group_imports = "One" +wrap_comments = true +comment_width = 100 diff --git a/.woodpecker.yml b/.woodpecker.yml index 65dc8a8a6..b77fd6615 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -2,7 +2,8 @@ # See https://github.com/woodpecker-ci/woodpecker/issues/1677 variables: - - &rust_image "rust:1.77" + - &rust_image "rust:1.78" + - &rust_nightly_image "rustlang/rust:nightly" - &install_pnpm "corepack enable pnpm" - &slow_check_paths - event: pull_request @@ -24,15 +25,17 @@ variables: "diesel.toml", ".gitmodules", ] - -# Broken for cron jobs currently, see -# https://github.com/woodpecker-ci/woodpecker/issues/1716 -# clone: -# git: -# image: woodpeckerci/plugin-git -# settings: -# recursive: true -# submodule_update_remote: true + - install_binstall: &install_binstall + - wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz + - tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz + - cp cargo-binstall /usr/local/cargo/bin + - install_diesel_cli: &install_diesel_cli + - apt update && apt install -y lsb-release build-essential + - sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - + - apt update && apt install -y postgresql-client-16 + - cargo install diesel_cli --no-default-features --features postgres + - export PATH="$CARGO_HOME/bin:$PATH" steps: prepare_repo: @@ -66,7 +69,7 @@ steps: - event: pull_request cargo_fmt: - image: rustlang/rust:nightly + image: *rust_nightly_image environment: # store cargo data in repo folder so that it gets cached between steps CARGO_HOME: .cargo_home @@ -77,11 +80,9 @@ steps: - event: pull_request cargo_machete: - image: rustlang/rust:nightly + image: *rust_nightly_image commands: - - wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz - - tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz - - cp cargo-binstall /usr/local/cargo/bin + - <<: *install_binstall - cargo binstall -y cargo-machete - cargo machete when: @@ -133,11 +134,12 @@ steps: when: *slow_check_paths check_diesel_schema: - image: willsquire/diesel-cli + image: *rust_image environment: CARGO_HOME: .cargo_home DATABASE_URL: postgres://lemmy:password@database:5432/lemmy commands: + - <<: *install_diesel_cli - diesel migration run - diesel print-schema --config-file=diesel.toml > tmp.schema - diff tmp.schema crates/db_schema/src/schema.rs @@ -197,8 +199,8 @@ steps: PGHOST: database PGDATABASE: lemmy commands: - - cargo install diesel_cli - - export PATH="$CARGO_HOME/bin:$PATH" + # Install diesel_cli + - <<: *install_diesel_cli # Run all migrations - diesel migration run # Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error) @@ -276,7 +278,9 @@ steps: publish_to_crates_io: image: *rust_image commands: - - cargo install cargo-workspaces + - <<: *install_binstall + # Install cargo-workspaces + - cargo binstall -y cargo-workspaces - cp -r migrations crates/db_schema/ - cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}" secrets: [cargo_api_token] diff --git a/Cargo.lock b/Cargo.lock index 29bdde002..de33fa443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,12 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", - "derive_builder 0.20.0", + "derive_builder", "dyn-clone", "enum_delegate", "futures", "futures-core", - "http", + "http 0.2.12", "http-signature-normalization", "http-signature-normalization-reqwest", "httpdate", @@ -41,8 +41,8 @@ dependencies = [ "openssl", "pin-project-lite", "regex", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "serde", "serde_json", "sha2", @@ -68,7 +68,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-sink", @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" dependencies = [ "actix-utils", "actix-web", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" +checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" dependencies = [ "actix-codec", "actix-rt", @@ -122,8 +122,8 @@ dependencies = [ "actix-tls", "actix-utils", "ahash", - "base64 0.21.7", - "bitflags 2.4.2", + "base64 0.22.1", + "bitflags 2.5.0", "brotli", "bytes", "bytestring", @@ -131,8 +131,8 @@ dependencies = [ "encoding_rs", "flate2", "futures-core", - "h2", - "http", + "h2 0.3.26", + "http 0.2.12", "httparse", "httpdate", "itoa", @@ -157,7 +157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -185,13 +185,14 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", - "http", - "regex", + "cfg-if", + "http 0.2.12", + "regex-lite", "serde", "tracing", ] @@ -236,9 +237,9 @@ dependencies = [ [[package]] name = "actix-tls" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cce60a2f2b477bc72e5cde0af1812a6e82d8fd85b5570a5dcf2a5bf2c5be5f" +checksum = "ac453898d866cdbecdbc2334fe1738c747b4eba14a677261f2b768ba05329389" dependencies = [ "actix-rt", "actix-service", @@ -248,11 +249,9 @@ dependencies = [ "pin-project-lite", "rustls-pki-types", "tokio", - "tokio-rustls 0.23.4", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-util", "tracing", - "webpki-roots 0.22.6", ] [[package]] @@ -267,9 +266,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.5.1" +version = "4.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" +checksum = "5d6316df3fa569627c98b12557a8b6ff0674e5be4bb9b5e4ae2550ddb4964ed6" dependencies = [ "actix-codec", "actix-http", @@ -296,7 +295,7 @@ dependencies = [ "mime", "once_cell", "pin-project-lite", - "regex", + "regex-lite", "serde", "serde_json", "serde_urlencoded", @@ -308,25 +307,25 @@ dependencies = [ [[package]] name = "actix-web-codegen" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "actix-web-httpauth" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775" +checksum = "456348ed9dcd72a13a1f4a660449fafdecee9ac8205552e286809eb5b0b29bd3" dependencies = [ "actix-utils", "actix-web", - "base64 0.21.7", + "base64 0.22.1", "futures-core", "futures-util", "log", @@ -335,15 +334,17 @@ dependencies = [ [[package]] name = "actix-web-prom" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23f332a652836b8f3a6876103c70c9ed436d0e69fa779ab5d7f57b1d5c8d488" +checksum = "76743e67d4e7efa9fc2ac7123de0dd7b2ca592668e19334f1d81a3b077afc6ac" dependencies = [ "actix-web", "futures-core", + "log", "pin-project-lite", "prometheus", "regex", + "strfmt", ] [[package]] @@ -376,9 +377,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -400,9 +401,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -421,47 +422,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -469,9 +471,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" dependencies = [ "backtrace", ] @@ -494,9 +496,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "flate2", "futures-core", @@ -507,11 +509,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -535,7 +537,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -546,20 +548,20 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "atom_syndication" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571832dcff775e26562e8e6930cd483de5587301d40d3a3b85d532b6383e15a7" +checksum = "f2f34613907f31c9dbef0240156db3c9263f34842b6e1a8999d2304ea62c8a30" dependencies = [ "chrono", - "derive_builder 0.12.0", + "derive_builder", "diligent-date-parser", "never", - "quick-xml 0.30.0", + "quick-xml 0.31.0", ] [[package]] @@ -569,28 +571,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] -name = "autocfg" -version = "1.1.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8487b59d62764df8231cb371c459314df895b41756df457a1fb1243d65c89195" +checksum = "474d7cec9d0a1126fad1b224b767fcbf351c23b0309bb21ec210bcfd379926a5" dependencies = [ "aws-lc-sys", "mirai-annotations", "paste", + "untrusted 0.7.1", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15eb61145320320eb919d9bab524617a7aa4216c78d342fae3a758bc33073e4" +checksum = "7505fc3cb7acbf42699a43a79dd9caa4ed9e99861dfbb837c5c0fb5a0a8d2980" dependencies = [ "bindgen", "cc", @@ -612,11 +621,11 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -638,8 +647,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -648,9 +657,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -706,7 +715,7 @@ dependencies = [ "async-trait", "futures-channel", "futures-util", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "tokio", ] @@ -748,10 +757,10 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "lazy_static", "lazycell", "log", @@ -761,7 +770,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.53", + "syn 2.0.66", "which", ] @@ -788,9 +797,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -822,9 +831,9 @@ checksum = "bfb5ea45aeb912f2dd334834e64ecf674a6673d57c73e9d12de0298b9bf98ee8" [[package]] name = "brotli" -version = "3.5.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -833,9 +842,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -843,15 +852,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -890,12 +899,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -931,7 +941,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -940,7 +950,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", "stacker", ] @@ -956,9 +966,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -967,9 +977,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -977,33 +987,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clokwerk" @@ -1058,15 +1068,15 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -1093,22 +1103,10 @@ dependencies = [ "ron", "serde", "serde_json", - "toml 0.8.12", + "toml 0.8.14", "yaml-rust", ] -[[package]] -name = "console-api" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" -dependencies = [ - "prost 0.11.9", - "prost-types 0.11.9", - "tonic 0.9.2", - "tracing-core", -] - [[package]] name = "console-api" version = "0.6.0" @@ -1116,49 +1114,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" dependencies = [ "futures-core", - "prost 0.12.3", - "prost-types 0.12.3", + "prost 0.12.6", + "prost-types", "tonic 0.10.2", "tracing-core", ] -[[package]] -name = "console-subscriber" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" -dependencies = [ - "console-api 0.5.0", - "crossbeam-channel", - "crossbeam-utils", - "futures", - "hdrhistogram", - "humantime", - "prost-types 0.11.9", - "serde", - "serde_json", - "thread_local", - "tokio", - "tokio-stream", - "tonic 0.9.2", - "tracing", - "tracing-core", - "tracing-subscriber", -] - [[package]] name = "console-subscriber" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" dependencies = [ - "console-api 0.6.0", + "console-api", "crossbeam-channel", "crossbeam-utils", "futures-task", "hdrhistogram", "humantime", - "prost-types 0.12.3", + "prost-types", "serde", "serde_json", "thread_local", @@ -1246,18 +1220,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -1273,9 +1247,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -1299,22 +1273,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - -[[package]] -name = "darling" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" -dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", + "darling_core 0.20.9", + "darling_macro 0.20.9", ] [[package]] @@ -1333,30 +1297,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 2.0.53", + "strsim 0.11.1", + "syn 2.0.66", ] [[package]] @@ -1372,24 +1322,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ - "darling_core 0.14.4", + "darling_core 0.20.9", "quote", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" -dependencies = [ - "darling_core 0.20.8", - "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -1399,10 +1338,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -1420,11 +1359,10 @@ dependencies = [ [[package]] name = "deadpool" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" +checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed" dependencies = [ - "async-trait", "deadpool-runtime", "num_cpus", "tokio", @@ -1432,23 +1370,37 @@ dependencies = [ [[package]] name = "deadpool-runtime" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" dependencies = [ "tokio", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1471,12 +1423,14 @@ dependencies = [ ] [[package]] -name = "derive_builder" -version = "0.12.0" +name = "derive-new" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ - "derive_builder_macro 0.12.0", + "proc-macro2", + "quote", + "syn 2.0.66", ] [[package]] @@ -1485,19 +1439,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" dependencies = [ - "derive_builder_macro 0.20.0", -] - -[[package]] -name = "derive_builder_core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", + "derive_builder_macro", ] [[package]] @@ -1506,20 +1448,10 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.53", -] - -[[package]] -name = "derive_builder_macro" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" -dependencies = [ - "derive_builder_core 0.12.0", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -1528,8 +1460,8 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ - "derive_builder_core 0.20.0", - "syn 2.0.53", + "derive_builder_core", + "syn 2.0.66", ] [[package]] @@ -1560,7 +1492,7 @@ version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "chrono", "diesel_derives", @@ -1596,7 +1528,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -1607,19 +1539,19 @@ checksum = "d5adf688c584fe33726ce0e2898f608a2a92578ac94a4a92fcecf73214fe0716" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "diesel_derives" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d02eecb814ae714ffe61ddc2db2dd03e6c49a42e269b5001355500d431cce0c" +checksum = "14701062d6bed917b5c7103bdffaee1e4609279e240488ad24e7bd979ca6866c" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -1649,7 +1581,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -1678,6 +1610,17 @@ dependencies = [ "chrono", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "doku" version = "0.21.1" @@ -1704,9 +1647,9 @@ dependencies = [ [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dunce" @@ -1722,9 +1665,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elementtree" @@ -1783,7 +1726,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -1818,9 +1761,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1828,20 +1771,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -1850,11 +1782,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 4.0.3", + "event-listener", "pin-project-lite", ] @@ -1895,9 +1827,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -1909,16 +1841,16 @@ dependencies = [ ] [[package]] -name = "finl_unicode" -version = "1.2.0" +name = "flagset" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -2036,7 +1968,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -2090,9 +2022,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -2115,17 +2047,36 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2149,9 +2100,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -2244,25 +2195,25 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be92446e11d68f5d71367d571c229d09ced1f24ab6d08ea0bff329d5f6c0b2a3" dependencies = [ - "html5ever", + "html5ever 0.26.0", "jni", "lazy_static", - "markup5ever_rcdom", + "markup5ever_rcdom 0.2.0", "percent-encoding", "regex", ] [[package]] name = "html2text" -version = "0.6.0" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cda84f06c1cc83476f79ae8e2e892b626bdadafcb227baec54c918cadc18a0" +checksum = "8c66ee488a63a92237d5b48875b7e05bb293be8fb2894641c8118b60c08ab5ef" dependencies = [ - "html5ever", - "markup5ever", + "html5ever 0.27.0", + "markup5ever 0.12.1", "tendril", + "thiserror", "unicode-width", - "xml5ever", ] [[package]] @@ -2273,12 +2224,26 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", - "markup5ever", + "markup5ever 0.11.0", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "http" version = "0.2.12" @@ -2290,6 +2255,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -2297,7 +2273,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -2320,8 +2319,8 @@ dependencies = [ "base64 0.13.1", "http-signature-normalization", "httpdate", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "sha2", "thiserror", "tokio", @@ -2347,17 +2346,17 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -2370,17 +2369,41 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.24.2" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http", - "hyper", - "rustls 0.21.12", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", + "tower-service", ] [[package]] @@ -2389,7 +2412,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -2402,12 +2425,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.29", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "i-love-jesus" version = "0.1.0" @@ -2425,7 +2468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8215279f83f9b829403812f845aa2d0dd5966332aa2fd0334a375256f3dd0322" dependencies = [ "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -2451,6 +2494,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2477,6 +2638,18 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +dependencies = [ + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", +] + [[package]] name = "image" version = "0.24.9" @@ -2515,12 +2688,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -2535,30 +2708,25 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -2568,15 +2736,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -2587,10 +2746,19 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.10" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2614,9 +2782,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -2632,13 +2800,14 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.7", - "pem 1.1.1", - "ring 0.16.20", + "js-sys", + "pem", + "ring", "serde", "serde_json", "simple_asn1", @@ -2664,7 +2833,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lemmy_api" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2675,6 +2844,7 @@ dependencies = [ "captcha", "chrono", "elementtree", + "hound", "lemmy_api_common", "lemmy_db_schema", "lemmy_db_views", @@ -2688,12 +2858,11 @@ dependencies = [ "totp-rs", "tracing", "url", - "wav", ] [[package]] name = "lemmy_api_common" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2714,8 +2883,8 @@ dependencies = [ "once_cell", "pretty_assertions", "regex", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "rosetta-i18n", "serde", "serde_with", @@ -2731,7 +2900,7 @@ dependencies = [ [[package]] name = "lemmy_api_crud" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "accept-language", "activitypub_federation", @@ -2754,7 +2923,7 @@ dependencies = [ [[package]] name = "lemmy_apub" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2767,8 +2936,8 @@ dependencies = [ "futures", "html2md", "html2text", - "http", - "itertools 0.12.1", + "http 0.2.12", + "itertools 0.13.0", "lemmy_api_common", "lemmy_db_schema", "lemmy_db_views", @@ -2777,7 +2946,7 @@ dependencies = [ "moka", "once_cell", "pretty_assertions", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_with", @@ -2792,7 +2961,7 @@ dependencies = [ [[package]] name = "lemmy_db_perf" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "anyhow", "clap", @@ -2807,14 +2976,15 @@ dependencies = [ [[package]] name = "lemmy_db_schema" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "anyhow", "async-trait", "bcrypt", "chrono", - "deadpool 0.10.0", + "deadpool 0.12.1", + "derive-new", "diesel", "diesel-async", "diesel-derive-enum", @@ -2828,7 +2998,7 @@ dependencies = [ "once_cell", "pretty_assertions", "regex", - "rustls 0.23.5", + "rustls 0.23.10", "serde", "serde_json", "serde_with", @@ -2837,7 +3007,7 @@ dependencies = [ "strum_macros", "tokio", "tokio-postgres", - "tokio-postgres-rustls 0.12.0", + "tokio-postgres-rustls", "tracing", "ts-rs", "typed-builder", @@ -2847,7 +3017,7 @@ dependencies = [ [[package]] name = "lemmy_db_views" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "actix-web", "chrono", @@ -2869,12 +3039,13 @@ dependencies = [ [[package]] name = "lemmy_db_views_actor" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "chrono", "diesel", "diesel-async", "lemmy_db_schema", + "lemmy_db_views", "lemmy_utils", "pretty_assertions", "serde", @@ -2889,7 +3060,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_moderator" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "diesel", "diesel-async", @@ -2901,7 +3072,7 @@ dependencies = [ [[package]] name = "lemmy_federate" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "anyhow", @@ -2913,10 +3084,12 @@ dependencies = [ "lemmy_apub", "lemmy_db_schema", "lemmy_db_views_actor", + "lemmy_utils", "moka", "once_cell", - "reqwest", + "reqwest 0.11.27", "serde_json", + "serial_test", "tokio", "tokio-util", "tracing", @@ -2924,7 +3097,7 @@ dependencies = [ [[package]] name = "lemmy_routes" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2937,8 +3110,8 @@ dependencies = [ "lemmy_db_views_actor", "lemmy_utils", "once_cell", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "rss", "serde", "tokio", @@ -2949,7 +3122,7 @@ dependencies = [ [[package]] name = "lemmy_server" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "activitypub_federation", "actix-cors", @@ -2958,7 +3131,7 @@ dependencies = [ "chrono", "clap", "clokwerk", - "console-subscriber 0.1.10", + "console-subscriber", "diesel", "diesel-async", "futures-util", @@ -2975,9 +3148,9 @@ dependencies = [ "pict-rs", "pretty_assertions", "prometheus", - "reqwest", - "reqwest-middleware", - "reqwest-tracing", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", + "reqwest-tracing 0.4.8", "serde_json", "serial_test", "tokio", @@ -2992,7 +3165,7 @@ dependencies = [ [[package]] name = "lemmy_utils" -version = "0.19.4-beta.7" +version = "0.19.4" dependencies = [ "actix-web", "anyhow", @@ -3003,16 +3176,16 @@ dependencies = [ "enum-map", "futures", "html2text", - "http", - "itertools 0.12.1", + "http 0.2.12", + "itertools 0.13.0", "lettre", "markdown-it", "once_cell", "openssl", "pretty_assertions", "regex", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "rosetta-build", "rosetta-i18n", "serde", @@ -3059,9 +3232,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -3070,17 +3243,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.48.5", ] [[package]] name = "line-wrap" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" [[package]] name = "linked-hash-map" @@ -3099,15 +3269,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "linux-raw-sys" -version = "0.4.13" +name = "litemap" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "local-channel" @@ -3128,9 +3298,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3192,7 +3362,21 @@ checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" dependencies = [ "log", "phf 0.10.1", - "phf_codegen", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.2", + "phf_codegen 0.11.2", "string_cache", "string_cache_codegen", "tendril", @@ -3204,10 +3388,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2" dependencies = [ - "html5ever", - "markup5ever", + "html5ever 0.26.0", + "markup5ever 0.11.0", "tendril", - "xml5ever", + "xml5ever 0.17.0", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever 0.27.0", + "markup5ever 0.12.1", + "tendril", + "xml5ever 0.18.1", ] [[package]] @@ -3225,6 +3421,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540f1c43aed89909c0cc0cc604e3bb2f7e7a341a3728a9e6cfe760e733cd11ed" + [[package]] name = "md-5" version = "0.10.6" @@ -3248,15 +3450,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "metrics" -version = "0.22.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" dependencies = [ "ahash", "portable-atomic", @@ -3264,30 +3466,33 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf4e7146e30ad172c42c39b3246864bd2d3c6396780711a1baf749cfe423e21" +checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" dependencies = [ - "base64 0.21.7", - "hyper", - "indexmap 2.2.5", + "base64 0.22.1", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "indexmap 2.2.6", "ipnet", "metrics", "metrics-util", "quanta", "thiserror", "tokio", + "tracing", ] [[package]] name = "metrics-util" -version = "0.16.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b07a5eb561b8cbc16be2d216faf7757f9baf3bfb94dbb0fae3df8387a5bb47f" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "metrics", "num_cpus", "quanta", @@ -3339,9 +3544,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", "simd-adler32", @@ -3376,10 +3581,10 @@ dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "event-listener 5.3.0", + "event-listener", "futures-util", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "quanta", "rustc_version", "smallvec", @@ -3403,11 +3608,10 @@ checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -3453,11 +3657,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3479,9 +3682,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -3517,7 +3720,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -3534,7 +3737,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -3545,9 +3748,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -3584,9 +3787,9 @@ dependencies = [ [[package]] name = "opentelemetry" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" dependencies = [ "futures-core", "futures-sink", @@ -3594,7 +3797,6 @@ dependencies = [ "once_cell", "pin-project-lite", "thiserror", - "urlencoding", ] [[package]] @@ -3606,7 +3808,7 @@ dependencies = [ "async-trait", "futures", "futures-util", - "http", + "http 0.2.12", "opentelemetry 0.19.0", "opentelemetry-proto 0.2.0", "prost 0.11.9", @@ -3617,18 +3819,17 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" dependencies = [ "async-trait", "futures-core", - "http", - "opentelemetry 0.22.0", - "opentelemetry-proto 0.5.0", - "opentelemetry-semantic-conventions", - "opentelemetry_sdk 0.22.1", - "prost 0.12.3", + "http 0.2.12", + "opentelemetry 0.23.0", + "opentelemetry-proto 0.6.0", + "opentelemetry_sdk 0.23.0", + "prost 0.12.6", "thiserror", "tokio", "tonic 0.11.0", @@ -3649,22 +3850,16 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" dependencies = [ - "opentelemetry 0.22.0", - "opentelemetry_sdk 0.22.1", - "prost 0.12.3", + "opentelemetry 0.23.0", + "opentelemetry_sdk 0.23.0", + "prost 0.12.6", "tonic 0.11.0", ] -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910" - [[package]] name = "opentelemetry_api" version = "0.19.0" @@ -3705,18 +3900,18 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", "glob", + "lazy_static", "once_cell", - "opentelemetry 0.22.0", + "opentelemetry 0.23.0", "ordered-float", "percent-encoding", "rand", @@ -3765,12 +3960,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -3789,22 +3984,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" @@ -3814,21 +4009,21 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pem" -version = "1.1.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", + "serde", ] [[package]] -name = "pem" -version = "3.0.3" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "base64 0.21.7", - "serde", + "base64ct", ] [[package]] @@ -3861,10 +4056,20 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ - "phf_generator", + "phf_generator 0.10.0", "phf_shared 0.10.0", ] +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", +] + [[package]] name = "phf_generator" version = "0.10.0" @@ -3875,6 +4080,16 @@ dependencies = [ "rand", ] +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + [[package]] name = "phf_shared" version = "0.10.0" @@ -3895,21 +4110,21 @@ dependencies = [ [[package]] name = "pict-rs" -version = "0.5.13" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7893e6e0d03847ff772f741b9b524544557274c56f2b0dad2a205260875b1d0f" +checksum = "5bbee61836cce10f7cf733196b7c0701e7ea6d0b617da68a3e6e4311b6262c2b" dependencies = [ "actix-form-data", "actix-web", "async-trait", "barrel", - "base64 0.21.7", + "base64 0.22.1", "bb8", "blurhash-update", "clap", "color-eyre", "config", - "console-subscriber 0.2.0", + "console-subscriber", "dashmap", "diesel", "diesel-async", @@ -3920,17 +4135,17 @@ dependencies = [ "metrics", "metrics-exporter-prometheus", "mime", - "opentelemetry 0.22.0", - "opentelemetry-otlp 0.15.0", - "opentelemetry_sdk 0.22.1", + "opentelemetry 0.23.0", + "opentelemetry-otlp 0.16.0", + "opentelemetry_sdk 0.23.0", "pin-project-lite", "refinery", - "reqwest", - "reqwest-middleware", - "reqwest-tracing", - "rustls 0.22.2", + "reqwest 0.12.4", + "reqwest-middleware 0.3.1", + "reqwest-tracing 0.5.0", + "rustls 0.23.10", "rustls-channel-resolver", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.1.2", "rusty-s3", "serde", "serde-tuple-vec-map", @@ -3944,18 +4159,18 @@ dependencies = [ "time", "tokio", "tokio-postgres", - "tokio-postgres-rustls 0.11.1", + "tokio-postgres-generic-rustls", "tokio-util", - "toml 0.8.12", + "toml 0.8.14", "tracing", "tracing-actix-web", "tracing-error", "tracing-log 0.2.0", - "tracing-opentelemetry 0.23.0", + "tracing-opentelemetry 0.24.0", "tracing-subscriber", "url", "uuid", - "webpki-roots 0.26.1", + "webpki-roots", ] [[package]] @@ -3975,7 +4190,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -3998,12 +4213,12 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" dependencies = [ "base64 0.21.7", - "indexmap 2.2.5", + "indexmap 2.2.6", "line-wrap", "quick-xml 0.31.0", "serde", @@ -4115,48 +4330,58 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 1.3.2", - "byteorder", + "bitflags 2.5.0", "hex", "lazy_static", - "rustix 0.36.17", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.5.0", + "hex", ] [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", "lazy_static", "libc", "memchr", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "procfs", "protobuf", "thiserror", @@ -4174,12 +4399,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.6", ] [[package]] @@ -4197,33 +4422,24 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.11.9", -] - -[[package]] -name = "prost-types" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" -dependencies = [ - "prost 0.12.3", + "prost 0.12.6", ] [[package]] @@ -4243,9 +4459,9 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", @@ -4262,7 +4478,6 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ - "encoding_rs", "memchr", "serde", ] @@ -4273,14 +4488,15 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ + "encoding_rs", "memchr", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -4323,11 +4539,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.0.1" +version = "11.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -4338,7 +4554,7 @@ checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -4360,10 +4576,19 @@ dependencies = [ ] [[package]] -name = "refinery" -version = "0.8.12" +name = "redox_syscall" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2783724569d96af53464d0711dff635cab7a4934df5e22e9fbc9e181523b83e" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "refinery" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0904191f0566c3d3e0091d5cc8dec22e663d77def2d247b16e7a438b188bf75d" dependencies = [ "refinery-core", "refinery-macros", @@ -4371,9 +4596,9 @@ dependencies = [ [[package]] name = "refinery-core" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d6c80329c0455510a8d42fce286ecb4b6bcd8c57e1816d9f2d6bd7379c2cc8" +checksum = "9bf253999e1899ae476c910b994959e341d84c4389ba9533d3dacbe06df04825" dependencies = [ "async-trait", "cfg-if", @@ -4386,34 +4611,35 @@ dependencies = [ "time", "tokio", "tokio-postgres", - "toml 0.8.12", + "toml 0.8.14", "url", "walkdir", ] [[package]] name = "refinery-macros" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab6e31e166a49d55cb09b62639e5ab9ba2e73f2f124336b06f6c321dc602779" +checksum = "bd81f69687fe8a1fa10995108b3ffc7cdbd63e682a4f8fbfd1020130780d7e17" dependencies = [ + "heck 0.4.1", "proc-macro2", "quote", "refinery-core", "regex", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4433,9 +4659,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-lite" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4444,9 +4676,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" @@ -4460,11 +4692,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-tls", "ipnet", "js-sys", @@ -4475,7 +4706,6 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4484,7 +4714,6 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -4492,8 +4721,50 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.4", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.25.0", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg 0.52.0", ] [[package]] @@ -4504,13 +4775,28 @@ checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" dependencies = [ "anyhow", "async-trait", - "http", - "reqwest", + "http 0.2.12", + "reqwest 0.11.27", "serde", "task-local-extensions", "thiserror", ] +[[package]] +name = "reqwest-middleware" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45d100244a467870f6cb763c4484d010a6bed6bd610b3676e3825d93fb4cfbd" +dependencies = [ + "anyhow", + "async-trait", + "http 1.1.0", + "reqwest 0.12.4", + "serde", + "thiserror", + "tower-service", +] + [[package]] name = "reqwest-tracing" version = "0.4.8" @@ -4520,15 +4806,31 @@ dependencies = [ "anyhow", "async-trait", "getrandom", - "matchit", + "matchit 0.7.3", "opentelemetry 0.16.0", - "reqwest", - "reqwest-middleware", + "reqwest 0.11.27", + "reqwest-middleware 0.2.5", "task-local-extensions", "tracing", "tracing-opentelemetry 0.16.0", ] +[[package]] +name = "reqwest-tracing" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b253954a1979e02eabccd7e9c3d61d8f86576108baa160775e7f160bb4e800a3" +dependencies = [ + "anyhow", + "async-trait", + "getrandom", + "http 1.1.0", + "matchit 0.8.2", + "reqwest 0.12.4", + "reqwest-middleware 0.3.1", + "tracing", +] + [[package]] name = "retain_mut" version = "0.1.9" @@ -4544,27 +4846,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "riff" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -4575,7 +4856,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -4587,7 +4868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -4614,21 +4895,21 @@ checksum = "2f8c01b9158de3aa5a7ac041a41c0e854d7adc3e473e7d7e2143eb5432bc5ba2" [[package]] name = "rss" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b2c77eb4450d7d5f98df52c381cd6c4e19b75dad9209a9530b85a44510219a" +checksum = "2f374fd66bb795938b78c021db1662d43a8ffbc42ec1ac25429fc4833b732751" dependencies = [ "atom_syndication", - "derive_builder 0.12.0", + "derive_builder", "never", - "quick-xml 0.30.0", + "quick-xml 0.31.0", ] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -4647,93 +4928,55 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.17" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustix" -version = "0.38.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" -dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.20.9" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.16.20", - "sct", - "webpki", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring 0.17.8", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" -dependencies = [ - "log", - "ring 0.17.8", + "ring", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.5" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-channel-resolver" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbd1941204442f051576a9a7ea8e8db074ad7fd43db1eb3378c3633f9f9e166" +checksum = "fede2a247359da6b4998f7723ec6468c2d6a577a5d8c17e54f21806426ad2290" dependencies = [ "nanorand", - "rustls 0.22.2", + "rustls 0.23.10", ] [[package]] @@ -4747,47 +4990,37 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "aws-lc-rs", - "ring 0.17.8", + "ring", "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-s3" @@ -4810,15 +5043,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -4829,6 +5056,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.23" @@ -4855,22 +5091,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sct" -version = "0.7.1" +name = "sdd" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] +checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -4879,9 +5111,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -4894,21 +5126,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f9da09dc3f4dfdb6374cbffff7a2cffcec316874d4429899eefdc97b3b94dcd" dependencies = [ "bit-set", - "html5ever", - "markup5ever_rcdom", + "html5ever 0.26.0", + "markup5ever_rcdom 0.2.0", ] [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -4924,22 +5156,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -4956,9 +5188,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -4985,7 +5217,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -4999,35 +5231,35 @@ version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "serial_test" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", - "parking_lot 0.12.1", + "once_cell", + "parking_lot 0.12.3", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -5069,9 +5301,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -5158,9 +5390,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smart-default" @@ -5170,25 +5402,19 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -5205,6 +5431,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stacker" version = "0.1.15" @@ -5228,6 +5460,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + [[package]] name = "string_cache" version = "0.8.7" @@ -5236,7 +5474,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -5248,7 +5486,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator", + "phf_generator 0.10.0", "phf_shared 0.10.0", "proc-macro2", "quote", @@ -5256,13 +5494,13 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] @@ -5279,27 +5517,27 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -5321,9 +5559,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -5336,6 +5574,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "syntect" version = "5.2.0" @@ -5349,7 +5598,7 @@ dependencies = [ "fnv", "once_cell", "plist", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "serde", "serde_derive", "serde_json", @@ -5402,7 +5651,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.31", + "rustix", "windows-sys 0.52.0", ] @@ -5428,22 +5677,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -5458,9 +5707,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -5479,9 +5728,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -5493,6 +5742,16 @@ version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a" +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -5509,17 +5768,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "tokio" -version = "1.37.0" +name = "tls_codec" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -5540,13 +5820,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -5572,7 +5852,7 @@ dependencies = [ "futures-channel", "futures-util", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "percent-encoding", "phf 0.11.2", "pin-project-lite", @@ -5586,18 +5866,17 @@ dependencies = [ ] [[package]] -name = "tokio-postgres-rustls" -version = "0.11.1" +name = "tokio-postgres-generic-rustls" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea13f22eda7127c827983bdaf0d7fff9df21c8817bab02815ac277a21143677" +checksum = "c8e98c31c29b2666fb28720739e11476166be4ead1610a37dcd7414bb124413a" dependencies = [ - "futures", - "ring 0.17.8", - "rustls 0.22.2", + "aws-lc-rs", + "rustls 0.23.10", "tokio", "tokio-postgres", - "tokio-rustls 0.25.0", - "x509-certificate", + "tokio-rustls 0.26.0", + "x509-cert", ] [[package]] @@ -5606,42 +5885,21 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" dependencies = [ - "ring 0.17.8", - "rustls 0.23.5", + "ring", + "rustls 0.23.10", "tokio", "tokio-postgres", "tokio-rustls 0.26.0", "x509-certificate", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.4", "rustls-pki-types", "tokio", ] @@ -5652,7 +5910,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.5", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] @@ -5670,16 +5928,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -5696,21 +5953,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.8", + "toml_edit 0.22.14", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -5721,7 +5978,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -5730,15 +5987,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.8" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.13", ] [[package]] @@ -5754,10 +6011,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -5773,34 +6030,6 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "tonic" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" -dependencies = [ - "async-trait", - "axum", - "base64 0.21.7", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost 0.11.9", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tonic" version = "0.10.2" @@ -5812,14 +6041,14 @@ dependencies = [ "axum", "base64 0.21.7", "bytes", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", + "prost 0.12.6", "tokio", "tokio-stream", "tower", @@ -5839,14 +6068,14 @@ dependencies = [ "axum", "base64 0.21.7", "bytes", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", + "prost 0.12.6", "tokio", "tokio-stream", "tower", @@ -5917,16 +6146,16 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684" +checksum = "4ee9e39a66d9b615644893ffc1704d2a89b5b315b7fd0228ad3182ca9a306b19" dependencies = [ "actix-web", "mutually_exclusive_features", - "opentelemetry 0.22.0", + "opentelemetry 0.23.0", "pin-project", "tracing", - "tracing-opentelemetry 0.23.0", + "tracing-opentelemetry 0.24.0", "uuid", ] @@ -5938,7 +6167,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -6022,14 +6251,14 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9be14ba1bbe4ab79e9229f7f89fab8d120b865859f10527f31c033e599d2284" +checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" dependencies = [ "js-sys", "once_cell", - "opentelemetry 0.22.0", - "opentelemetry_sdk 0.22.1", + "opentelemetry 0.23.0", + "opentelemetry_sdk 0.23.0", "smallvec", "tracing", "tracing-core", @@ -6071,9 +6300,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" +checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90" [[package]] name = "try-lock" @@ -6101,7 +6330,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", "termcolor", ] @@ -6122,7 +6351,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", ] [[package]] @@ -6168,10 +6397,16 @@ dependencies = [ ] [[package]] -name = "unicode-width" -version = "0.1.11" +name = "unicode-properties" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -6193,12 +6428,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.0", "percent-encoding", "serde", ] @@ -6215,12 +6450,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.1" @@ -6308,7 +6555,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -6342,7 +6589,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6366,15 +6613,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wav" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" -dependencies = [ - "riff", -] - [[package]] name = "web-sys" version = "0.3.69" @@ -6403,7 +6641,7 @@ checksum = "8d07b90492f7b6fe35f5298fcd01c663d3c453e8c302dc86c7292c6681b8117d" dependencies = [ "anyhow", "nom", - "reqwest", + "reqwest 0.11.27", "select", "serde", "thiserror", @@ -6412,46 +6650,22 @@ dependencies = [ [[package]] name = "webpage" -version = "1.6.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8598785beeb5af95abe95e7bb20c7e747d1188347080d6811d5a56d2b9a5f368" +checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac" dependencies = [ - "html5ever", - "markup5ever_rcdom", + "html5ever 0.27.0", + "markup5ever_rcdom 0.3.0", "serde", "serde_json", -] - -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "url", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "webpki-roots" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" dependencies = [ "rustls-pki-types", ] @@ -6465,7 +6679,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.31", + "rustix", ] [[package]] @@ -6497,11 +6711,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -6517,7 +6731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -6526,16 +6740,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.5", ] [[package]] @@ -6553,22 +6758,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.5", ] [[package]] @@ -6588,25 +6778,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6615,15 +6800,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -6633,15 +6812,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -6651,15 +6824,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -6669,15 +6842,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -6687,15 +6854,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -6705,15 +6866,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -6723,9 +6878,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -6738,9 +6893,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -6755,6 +6910,40 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", + "tls_codec", +] + [[package]] name = "x509-certificate" version = "0.23.1" @@ -6766,8 +6955,8 @@ dependencies = [ "chrono", "der", "hex", - "pem 3.0.3", - "ring 0.17.8", + "pem", + "ring", "signature", "spki", "thiserror", @@ -6788,7 +6977,18 @@ checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" dependencies = [ "log", "mac", - "markup5ever", + "markup5ever 0.11.0", +] + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever 0.12.1", ] [[package]] @@ -6807,30 +7007,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] -name = "zerocopy" -version = "0.7.32" +name = "yoke" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -6843,32 +7088,54 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.66", +] + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", ] [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index bbdce4a75..b085773c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.19.4-beta.7" +version = "0.19.4" edition = "2021" description = "A link aggregator for the fediverse" license = "AGPL-3.0" @@ -67,8 +67,8 @@ members = [ [workspace.lints.clippy] cast_lossless = "deny" -complexity = "deny" -correctness = "deny" +complexity = { level = "deny", priority = -1 } +correctness = { level = "deny", priority = -1 } dbg_macro = "deny" explicit_into_iter_loop = "deny" explicit_iter_loop = "deny" @@ -79,44 +79,44 @@ inefficient_to_string = "deny" items-after-statements = "deny" manual_string_new = "deny" needless_collect = "deny" -perf = "deny" +perf = { level = "deny", priority = -1 } redundant_closure_for_method_calls = "deny" -style = "deny" -suspicious = "deny" +style = { level = "deny", priority = -1 } +suspicious = { level = "deny", priority = -1 } uninlined_format_args = "allow" unused_self = "deny" unwrap_used = "deny" [workspace.dependencies] -lemmy_api = { version = "=0.19.4-beta.7", path = "./crates/api" } -lemmy_api_crud = { version = "=0.19.4-beta.7", path = "./crates/api_crud" } -lemmy_apub = { version = "=0.19.4-beta.7", path = "./crates/apub" } -lemmy_utils = { version = "=0.19.4-beta.7", path = "./crates/utils", default-features = false } -lemmy_db_schema = { version = "=0.19.4-beta.7", path = "./crates/db_schema" } -lemmy_api_common = { version = "=0.19.4-beta.7", path = "./crates/api_common" } -lemmy_routes = { version = "=0.19.4-beta.7", path = "./crates/routes" } -lemmy_db_views = { version = "=0.19.4-beta.7", path = "./crates/db_views" } -lemmy_db_views_actor = { version = "=0.19.4-beta.7", path = "./crates/db_views_actor" } -lemmy_db_views_moderator = { version = "=0.19.4-beta.7", path = "./crates/db_views_moderator" } -lemmy_federate = { version = "=0.19.4-beta.7", path = "./crates/federate" } +lemmy_api = { version = "=0.19.4", path = "./crates/api" } +lemmy_api_crud = { version = "=0.19.4", path = "./crates/api_crud" } +lemmy_apub = { version = "=0.19.4", path = "./crates/apub" } +lemmy_utils = { version = "=0.19.4", path = "./crates/utils", default-features = false } +lemmy_db_schema = { version = "=0.19.4", path = "./crates/db_schema" } +lemmy_api_common = { version = "=0.19.4", path = "./crates/api_common" } +lemmy_routes = { version = "=0.19.4", path = "./crates/routes" } +lemmy_db_views = { version = "=0.19.4", path = "./crates/db_views" } +lemmy_db_views_actor = { version = "=0.19.4", path = "./crates/db_views_actor" } +lemmy_db_views_moderator = { version = "=0.19.4", path = "./crates/db_views_moderator" } +lemmy_federate = { version = "=0.19.4", path = "./crates/federate" } activitypub_federation = { version = "0.5.6", default-features = false, features = [ "actix-web", ] } diesel = "2.1.6" diesel_migrations = "2.1.0" diesel-async = "0.4.1" -serde = { version = "1.0.199", features = ["derive"] } +serde = { version = "1.0.203", features = ["derive"] } serde_with = "3.8.1" -actix-web = { version = "4.5.1", default-features = false, features = [ +actix-web = { version = "4.6.0", default-features = false, features = [ "macros", - "rustls", + "rustls-0_23", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", ] } tracing = "0.1.40" -tracing-actix-web = { version = "0.7.10", default-features = false } +tracing-actix-web = { version = "0.7.11", default-features = false } tracing-error = "0.2.0" tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } @@ -128,25 +128,25 @@ clokwerk = "0.4.0" doku = { version = "0.21.1", features = ["url-2"] } bcrypt = "0.15.1" chrono = { version = "0.4.38", features = ["serde"], default-features = false } -serde_json = { version = "1.0.116", features = ["preserve_order"] } +serde_json = { version = "1.0.117", features = ["preserve_order"] } base64 = "0.22.1" uuid = { version = "1.8.0", features = ["serde", "v4"] } async-trait = "0.1.80" captcha = "0.0.9" -anyhow = { version = "1.0.82", features = [ +anyhow = { version = "1.0.86", features = [ "backtrace", ] } # backtrace is on by default on nightly, but not stable rust diesel_ltree = "0.3.1" typed-builder = "0.18.2" -serial_test = "2.0.0" -tokio = { version = "1.37.0", features = ["full"] } +serial_test = "3.1.1" +tokio = { version = "1.38.0", features = ["full"] } regex = "1.10.4" once_cell = "1.19.0" diesel-derive-newtype = "2.1.2" diesel-derive-enum = { version = "2.1.0", features = ["postgres"] } -strum = "0.25.0" -strum_macros = "0.25.3" -itertools = "0.12.1" +strum = "0.26.2" +strum_macros = "0.26.4" +itertools = "0.13.0" futures = "0.3.30" http = "0.2.12" rosetta-i18n = "0.1.3" @@ -157,7 +157,7 @@ ts-rs = { version = "7.1.1", features = [ "chrono-impl", "no-serde-warnings", ] } -rustls = { version = "0.23.5", features = ["ring"] } +rustls = { version = "0.23.9", features = ["ring"] } futures-util = "0.3.30" tokio-postgres = "0.7.10" tokio-postgres-rustls = "0.12.0" @@ -165,8 +165,9 @@ urlencoding = "2.1.3" enum-map = "2.7" moka = { version = "0.12.7", features = ["future"] } i-love-jesus = { version = "0.1.0" } -clap = { version = "4.5.4", features = ["derive"] } +clap = { version = "4.5.6", features = ["derive", "env"] } pretty_assertions = "1.4.0" +derive-new = "0.6.0" [dependencies] lemmy_api = { workspace = true } @@ -194,17 +195,17 @@ clokwerk = { workspace = true } serde_json = { workspace = true } tracing-opentelemetry = { workspace = true, optional = true } opentelemetry = { workspace = true, optional = true } -console-subscriber = { version = "0.1.10", optional = true } +console-subscriber = { version = "0.3.0", optional = true } opentelemetry-otlp = { version = "0.12.0", optional = true } -pict-rs = { version = "0.5.13", optional = true } +pict-rs = { version = "0.5.15", optional = true } tokio.workspace = true -actix-cors = "0.6.5" +actix-cors = "0.7.0" futures-util = { workspace = true } chrono = { workspace = true } -prometheus = { version = "0.13.3", features = ["process"] } +prometheus = { version = "0.13.4", features = ["process"] } serial_test = { workspace = true } clap = { workspace = true } -actix-web-prom = "0.7.0" +actix-web-prom = "0.8.0" [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/api_tests/.eslintrc.json b/api_tests/.eslintrc.json deleted file mode 100644 index 75b1706aa..000000000 --- a/api_tests/.eslintrc.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "root": true, - "env": { - "browser": true - }, - "plugins": ["@typescript-eslint"], - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "warnOnUnsupportedTypeScriptVersion": false - }, - "rules": { - "@typescript-eslint/ban-ts-comment": 0, - "@typescript-eslint/no-explicit-any": 0, - "@typescript-eslint/explicit-module-boundary-types": 0, - "@typescript-eslint/no-var-requires": 0, - "arrow-body-style": 0, - "curly": 0, - "eol-last": 0, - "eqeqeq": 0, - "func-style": 0, - "import/no-duplicates": 0, - "max-statements": 0, - "max-params": 0, - "new-cap": 0, - "no-console": 0, - "no-duplicate-imports": 0, - "no-extra-parens": 0, - "no-return-assign": 0, - "no-throw-literal": 0, - "no-trailing-spaces": 0, - "no-unused-expressions": 0, - "no-useless-constructor": 0, - "no-useless-escape": 0, - "no-var": 0, - "prefer-const": 0, - "prefer-rest-params": 0, - "quote-props": 0, - "unicorn/filename-case": 0 - } -} diff --git a/api_tests/eslint.config.mjs b/api_tests/eslint.config.mjs new file mode 100644 index 000000000..35b6a3f48 --- /dev/null +++ b/api_tests/eslint.config.mjs @@ -0,0 +1,50 @@ +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default [ + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parser: tseslint.parser, + }, + }, + // For some reason this has to be in its own block + { + ignores: ["putTypesInIndex.js", "dist/*", "docs/*", ".yalc", "jest.config.js"], + }, + { + files: ["src/**/*"], + rules: { + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-var-requires": 0, + "arrow-body-style": 0, + curly: 0, + "eol-last": 0, + eqeqeq: 0, + "func-style": 0, + "import/no-duplicates": 0, + "max-statements": 0, + "max-params": 0, + "new-cap": 0, + "no-console": 0, + "no-duplicate-imports": 0, + "no-extra-parens": 0, + "no-return-assign": 0, + "no-throw-literal": 0, + "no-trailing-spaces": 0, + "no-unused-expressions": 0, + "no-useless-constructor": 0, + "no-useless-escape": 0, + "no-var": 0, + "prefer-const": 0, + "prefer-rest-params": 0, + "quote-props": 0, + "unicorn/filename-case": 0, + }, + }, +]; diff --git a/api_tests/package.json b/api_tests/package.json index b194dae30..9d4ead158 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -6,9 +6,9 @@ "repository": "https://github.com/LemmyNet/lemmy", "author": "Dessalines", "license": "AGPL-3.0", - "packageManager": "pnpm@9.1.1+sha256.9551e803dcb7a1839fdf5416153a844060c7bce013218ce823410532504ac10b", + "packageManager": "pnpm@9.3.0", "scripts": { - "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'", + "lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'", "fix": "prettier --write src && eslint --fix src", "api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ", "api-test-follow": "jest -i follow.spec.ts", @@ -25,12 +25,13 @@ "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", "download-file-sync": "^1.0.4", - "eslint": "^8.57.0", + "eslint": "^9.0.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", - "lemmy-js-client": "0.19.4-alpha.18", + "lemmy-js-client": "0.19.5-alpha.1", "prettier": "^3.2.5", "ts-jest": "^29.1.0", - "typescript": "^5.4.4" + "typescript": "^5.4.4", + "typescript-eslint": "^7.13.0" } } diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 42eb612e5..ffe82ac5c 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -13,37 +13,40 @@ importers: version: 29.5.12 '@types/node': specifier: ^20.12.4 - version: 20.12.4 + version: 20.14.2 '@typescript-eslint/eslint-plugin': specifier: ^7.5.0 - version: 7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.4) + version: 7.13.0(@typescript-eslint/parser@7.13.0(eslint@9.5.0)(typescript@5.4.5))(eslint@9.5.0)(typescript@5.4.5) '@typescript-eslint/parser': specifier: ^7.5.0 - version: 7.5.0(eslint@8.57.0)(typescript@5.4.4) + version: 7.13.0(eslint@9.5.0)(typescript@5.4.5) download-file-sync: specifier: ^1.0.4 version: 1.0.4 eslint: - specifier: ^8.57.0 - version: 8.57.0 + specifier: ^9.0.0 + version: 9.5.0 eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.1.3(eslint@8.57.0)(prettier@3.2.5) + version: 5.1.3(eslint@9.5.0)(prettier@3.3.2) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.12.4) + version: 29.7.0(@types/node@20.14.2) lemmy-js-client: - specifier: 0.19.4-alpha.18 - version: 0.19.4-alpha.18 + specifier: 0.19.5-alpha.1 + version: 0.19.5-alpha.1 prettier: specifier: ^3.2.5 - version: 3.2.5 + version: 3.3.2 ts-jest: specifier: ^29.1.0 - version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.4) + version: 29.1.4(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.14.2))(typescript@5.4.5) typescript: specifier: ^5.4.4 - version: 5.4.4 + version: 5.4.5 + typescript-eslint: + specifier: ^7.13.0 + version: 7.13.0(eslint@9.5.0)(typescript@5.4.5) packages: @@ -232,24 +235,33 @@ packages: resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint-community/regexpp@4.10.1': + resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.16.0': + resolution: {integrity: sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.5.0': + resolution: {integrity: sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.2': - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -395,14 +407,8 @@ packages: '@types/jest@29.5.12': resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/node@20.12.4': - resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==} - - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/node@20.14.2': + resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -413,8 +419,8 @@ packages: '@types/yargs@17.0.32': resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} - '@typescript-eslint/eslint-plugin@7.5.0': - resolution: {integrity: sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==} + '@typescript-eslint/eslint-plugin@7.13.0': + resolution: {integrity: sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -424,8 +430,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.5.0': - resolution: {integrity: sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==} + '@typescript-eslint/parser@7.13.0': + resolution: {integrity: sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -434,12 +440,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.5.0': - resolution: {integrity: sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==} + '@typescript-eslint/scope-manager@7.13.0': + resolution: {integrity: sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@7.5.0': - resolution: {integrity: sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==} + '@typescript-eslint/type-utils@7.13.0': + resolution: {integrity: sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -448,12 +454,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@7.5.0': - resolution: {integrity: sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==} + '@typescript-eslint/types@7.13.0': + resolution: {integrity: sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@7.5.0': - resolution: {integrity: sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==} + '@typescript-eslint/typescript-estree@7.13.0': + resolution: {integrity: sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -461,19 +467,16 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.5.0': - resolution: {integrity: sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==} + '@typescript-eslint/utils@7.13.0': + resolution: {integrity: sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@7.5.0': - resolution: {integrity: sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==} + '@typescript-eslint/visitor-keys@7.13.0': + resolution: {integrity: sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==} engines: {node: ^18.18.0 || >=20.0.0} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -559,6 +562,10 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + browserslist@4.22.3: resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -656,6 +663,15 @@ packages: supports-color: optional: true + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dedent@1.5.1: resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} peerDependencies: @@ -683,10 +699,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - download-file-sync@1.0.4: resolution: {integrity: sha512-vH92qNH508jZZA12HQNq/aiMDfagr4JvjFiI17Bi8oYjsxwv5ZVIi7iHkYmUXxOQUr90tcVX+8EPePjAqG1Y0w==} @@ -733,22 +745,26 @@ packages: eslint-config-prettier: optional: true - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.0.1: + resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.5.0: + resolution: {integrity: sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@10.0.1: + resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -805,14 +821,18 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -821,9 +841,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} @@ -865,14 +885,15 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} @@ -922,6 +943,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1156,8 +1178,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lemmy-js-client@0.19.4-alpha.18: - resolution: {integrity: sha512-CUKRIiINZF2zOfK5WzBDF071LjMmRBFHwiSYBMGJyQP1zu8sPKCb/ptg25WWrf79Y4uOaVLctgHg3oEUXmSUmQ==} + lemmy-js-client@0.19.5-alpha.1: + resolution: {integrity: sha512-GOhaiTQzrpwdmc3DFYemT2SmNmpuQJe2BWUms9QOzdYlkA1WZ0uu7axPE3s+T5OOxfy7K9Q2gsLe72dcVSlffw==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -1187,10 +1209,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -1212,6 +1230,10 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1219,8 +1241,8 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} ms@2.1.2: @@ -1324,8 +1346,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + prettier@3.3.2: + resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} hasBin: true @@ -1378,10 +1400,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1389,13 +1407,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - - semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} hasBin: true @@ -1499,12 +1512,13 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-jest@29.1.2: - resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} - engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + ts-jest@29.1.4: + resolution: {integrity: sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 '@jest/types': ^29.0.0 babel-jest: ^29.0.0 esbuild: '*' @@ -1513,6 +1527,8 @@ packages: peerDependenciesMeta: '@babel/core': optional: true + '@jest/transform': + optional: true '@jest/types': optional: true babel-jest: @@ -1531,16 +1547,22 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript@5.4.4: - resolution: {integrity: sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==} + typescript-eslint@7.13.0: + resolution: {integrity: sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true @@ -1586,9 +1608,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1630,7 +1649,7 @@ snapshots: '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1796,7 +1815,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - debug: 4.3.4 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -1809,19 +1828,29 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.5.0)': dependencies: - eslint: 8.57.0 + eslint: 9.5.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.10.0': {} - '@eslint/eslintrc@2.1.4': + '@eslint-community/regexpp@4.10.1': {} + + '@eslint/config-array@0.16.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.6.1 - globals: 13.24.0 + espree: 10.0.1 + globals: 14.0.0 ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -1830,19 +1859,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@9.5.0': {} - '@humanwhocodes/config-array@0.11.14': - dependencies: - '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + '@eslint/object-schema@2.1.4': {} '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.2': {} + '@humanwhocodes/retry@0.3.0': {} '@istanbuljs/load-nyc-config@1.1.0': dependencies: @@ -1857,7 +1880,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -1870,14 +1893,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.4) + jest-config: 29.7.0(@types/node@20.14.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -1902,7 +1925,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -1920,7 +1943,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.4 + '@types/node': 20.14.2 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -1942,7 +1965,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.22 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2012,7 +2035,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.4 + '@types/node': 20.14.2 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -2080,7 +2103,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.12.4 + '@types/node': 20.14.2 '@types/istanbul-lib-coverage@2.0.6': {} @@ -2097,14 +2120,10 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 - '@types/json-schema@7.0.15': {} - - '@types/node@20.12.4': + '@types/node@20.14.2': dependencies: undici-types: 5.26.5 - '@types/semver@7.5.8': {} - '@types/stack-utils@2.0.3': {} '@types/yargs-parser@21.0.3': {} @@ -2113,90 +2132,87 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.4)': + '@typescript-eslint/eslint-plugin@7.13.0(@typescript-eslint/parser@7.13.0(eslint@9.5.0)(typescript@5.4.5))(eslint@9.5.0)(typescript@5.4.5)': dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/scope-manager': 7.5.0 - '@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.5.0 - debug: 4.3.4 - eslint: 8.57.0 + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.13.0 + '@typescript-eslint/type-utils': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.13.0 + eslint: 9.5.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.4)': + '@typescript-eslint/parser@7.13.0(eslint@9.5.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/scope-manager': 7.5.0 - '@typescript-eslint/types': 7.5.0 - '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.5.0 - debug: 4.3.4 - eslint: 8.57.0 - typescript: 5.4.4 + '@typescript-eslint/scope-manager': 7.13.0 + '@typescript-eslint/types': 7.13.0 + '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.13.0 + debug: 4.3.5 + eslint: 9.5.0 + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.5.0': + '@typescript-eslint/scope-manager@7.13.0': dependencies: - '@typescript-eslint/types': 7.5.0 - '@typescript-eslint/visitor-keys': 7.5.0 + '@typescript-eslint/types': 7.13.0 + '@typescript-eslint/visitor-keys': 7.13.0 - '@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.4)': + '@typescript-eslint/type-utils@7.13.0(eslint@9.5.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4) - '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4) - debug: 4.3.4 - eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + debug: 4.3.5 + eslint: 9.5.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.5.0': {} + '@typescript-eslint/types@7.13.0': {} - '@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.4)': + '@typescript-eslint/typescript-estree@7.13.0(typescript@5.4.5)': dependencies: - '@typescript-eslint/types': 7.5.0 - '@typescript-eslint/visitor-keys': 7.5.0 - debug: 4.3.4 + '@typescript-eslint/types': 7.13.0 + '@typescript-eslint/visitor-keys': 7.13.0 + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.4)': + '@typescript-eslint/utils@7.13.0(eslint@9.5.0)(typescript@5.4.5)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.5.0 - '@typescript-eslint/types': 7.5.0 - '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4) - eslint: 8.57.0 - semver: 7.6.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.5.0) + '@typescript-eslint/scope-manager': 7.13.0 + '@typescript-eslint/types': 7.13.0 + '@typescript-eslint/typescript-estree': 7.13.0(typescript@5.4.5) + eslint: 9.5.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.5.0': + '@typescript-eslint/visitor-keys@7.13.0': dependencies: - '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/types': 7.13.0 eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.2.0': {} - acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 @@ -2306,6 +2322,10 @@ snapshots: dependencies: fill-range: 7.0.1 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + browserslist@4.22.3: dependencies: caniuse-lite: 1.0.30001581 @@ -2374,13 +2394,13 @@ snapshots: convert-source-map@2.0.0: {} - create-jest@29.7.0(@types/node@20.12.4): + create-jest@29.7.0(@types/node@20.14.2): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.12.4) + jest-config: 29.7.0(@types/node@20.14.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -2399,6 +2419,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.5: + dependencies: + ms: 2.1.2 + dedent@1.5.1: {} deep-is@0.1.4: {} @@ -2413,10 +2437,6 @@ snapshots: dependencies: path-type: 4.0.0 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - download-file-sync@1.0.4: {} electron-to-chromium@1.4.648: {} @@ -2437,52 +2457,50 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.2.5): + eslint-plugin-prettier@5.1.3(eslint@9.5.0)(prettier@3.3.2): dependencies: - eslint: 8.57.0 - prettier: 3.2.5 + eslint: 9.5.0 + prettier: 3.3.2 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 - eslint-scope@7.2.2: + eslint-scope@8.0.1: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint-visitor-keys@4.0.0: {} + + eslint@9.5.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.5.0) '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint/config-array': 0.16.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.5.0 '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.4 - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.0.1 + eslint-visitor-keys: 4.0.0 + espree: 10.0.1 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -2494,11 +2512,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@9.6.1: + espree@10.0.1: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 4.0.0 esprima@4.0.1: {} @@ -2546,7 +2564,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.5 + micromatch: 4.0.7 fast-json-stable-stringify@2.1.0: {} @@ -2560,14 +2578,18 @@ snapshots: dependencies: bser: 2.1.1 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -2578,11 +2600,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.3.1 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.3.1: {} @@ -2620,9 +2641,7 @@ snapshots: globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@14.0.0: {} globby@11.1.0: dependencies: @@ -2712,7 +2731,7 @@ snapshots: '@babel/parser': 7.23.9 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color @@ -2724,7 +2743,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.5 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -2747,7 +2766,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -2767,16 +2786,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.12.4): + jest-cli@29.7.0(@types/node@20.14.2): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.12.4) + create-jest: 29.7.0(@types/node@20.14.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.12.4) + jest-config: 29.7.0(@types/node@20.14.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -2786,12 +2805,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.12.4): + jest-config@29.7.0(@types/node@20.14.2): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 babel-jest: 29.7.0(@babel/core@7.23.9) chalk: 4.1.2 ci-info: 3.9.0 @@ -2811,6 +2829,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.14.2 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -2839,7 +2859,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2849,7 +2869,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.12.4 + '@types/node': 20.14.2 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -2888,11 +2908,11 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -2923,7 +2943,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2951,7 +2971,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -2990,14 +3010,14 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -3016,7 +3036,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.4 + '@types/node': 20.14.2 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3025,17 +3045,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.12.4 + '@types/node': 20.14.2 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.12.4): + jest@29.7.0(@types/node@20.14.2): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.12.4) + jest-cli: 29.7.0(@types/node@20.14.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3071,7 +3091,7 @@ snapshots: kleur@3.0.3: {} - lemmy-js-client@0.19.4-alpha.18: {} + lemmy-js-client@0.19.5-alpha.1: {} leven@3.1.0: {} @@ -3098,13 +3118,9 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - make-dir@4.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.2 make-error@1.3.6: {} @@ -3121,13 +3137,18 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mimic-fn@2.1.0: {} minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.3: + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 @@ -3217,7 +3238,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.2.5: {} + prettier@3.3.2: {} pretty-format@29.7.0: dependencies: @@ -3258,23 +3279,13 @@ snapshots: reusify@1.0.4: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 semver@6.3.1: {} - semver@7.5.4: - dependencies: - lru-cache: 6.0.0 - - semver@7.6.0: - dependencies: - lru-cache: 6.0.0 + semver@7.6.2: {} shebang-command@2.0.0: dependencies: @@ -3357,23 +3368,27 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@1.3.0(typescript@5.4.4): + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: - typescript: 5.4.4 + typescript: 5.4.5 - ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.4): + ts-jest@29.1.4(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.14.2))(typescript@5.4.5): dependencies: - '@babel/core': 7.23.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.12.4) + jest: 29.7.0(@types/node@20.14.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.5.4 - typescript: 5.4.4 + semver: 7.6.2 + typescript: 5.4.5 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.23.9 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.23.9) tslib@2.6.2: {} @@ -3383,11 +3398,20 @@ snapshots: type-detect@4.0.8: {} - type-fest@0.20.2: {} - type-fest@0.21.3: {} - typescript@5.4.4: {} + typescript-eslint@7.13.0(eslint@9.5.0)(typescript@5.4.5): + dependencies: + '@typescript-eslint/eslint-plugin': 7.13.0(@typescript-eslint/parser@7.13.0(eslint@9.5.0)(typescript@5.4.5))(eslint@9.5.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.13.0(eslint@9.5.0)(typescript@5.4.5) + eslint: 9.5.0 + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + typescript@5.4.5: {} undici-types@5.26.5: {} @@ -3432,8 +3456,6 @@ snapshots: yallist@3.1.1: {} - yallist@4.0.0: {} - yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/api_tests/prepare-drone-federation-test.sh b/api_tests/prepare-drone-federation-test.sh index 6249348f9..65c4827d9 100755 --- a/api_tests/prepare-drone-federation-test.sh +++ b/api_tests/prepare-drone-federation-test.sh @@ -3,19 +3,19 @@ # it is expected that this script is called by run-federation-test.sh script. set -e -if [ -n "$LEMMY_LOG_LEVEL" ]; +if [ -z "$LEMMY_LOG_LEVEL" ]; then - LEMMY_LOG_LEVEL=warn + LEMMY_LOG_LEVEL=info fi export RUST_BACKTRACE=1 -#export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL" +export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL" export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min # pictrs setup if [ ! -f "api_tests/pict-rs" ]; then - curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.13/pict-rs-linux-amd64" -o api_tests/pict-rs + curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs chmod +x api_tests/pict-rs fi ./api_tests/pict-rs \ diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index dfab4109c..8c3a23ab5 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -37,8 +37,9 @@ import { followCommunity, blockCommunity, delay, + saveUserSettings, } from "./shared"; -import { CommentView, CommunityView } from "lemmy-js-client"; +import { CommentView, CommunityView, SaveUserSettings } from "lemmy-js-client"; let betaCommunity: CommunityView | undefined; let postOnAlphaRes: PostResponse; @@ -443,6 +444,59 @@ test("Reply to a comment from another instance, get notification", async () => { assertCommentFederation(alphaReply, replyRes.comment_view); }); +test("Bot reply notifications are filtered when bots are hidden", async () => { + const newAlphaBot = await registerUser(alpha, alphaUrl); + let form: SaveUserSettings = { + bot_account: true, + }; + await saveUserSettings(newAlphaBot, form); + + const alphaCommunity = ( + await resolveCommunity(alpha, "!main@lemmy-alpha:8541") + ).community; + + if (!alphaCommunity) { + throw "Missing alpha community"; + } + + await alpha.markAllAsRead(); + form = { + show_bot_accounts: false, + }; + await saveUserSettings(alpha, form); + const postOnAlphaRes = await createPost(alpha, alphaCommunity.community.id); + + // Bot reply to alpha's post + let commentRes = await createComment( + newAlphaBot, + postOnAlphaRes.post_view.post.id, + ); + expect(commentRes).toBeDefined(); + + let alphaUnreadCountRes = await getUnreadCount(alpha); + expect(alphaUnreadCountRes.replies).toBe(0); + + let alphaUnreadRepliesRes = await getReplies(alpha, true); + expect(alphaUnreadRepliesRes.replies.length).toBe(0); + + // This both restores the original state that may be expected by other tests + // implicitly and is used by the next steps to ensure replies are still + // returned when a user later decides to show bot accounts again. + form = { + show_bot_accounts: true, + }; + await saveUserSettings(alpha, form); + + alphaUnreadCountRes = await getUnreadCount(alpha); + expect(alphaUnreadCountRes.replies).toBe(1); + + alphaUnreadRepliesRes = await getReplies(alpha, true); + expect(alphaUnreadRepliesRes.replies.length).toBe(1); + expect(alphaUnreadRepliesRes.replies[0].comment.id).toBe( + commentRes.comment_view.comment.id, + ); +}); + test("Mention beta from alpha", async () => { if (!betaCommunity) throw Error("no community"); const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id); diff --git a/api_tests/src/image.spec.ts b/api_tests/src/image.spec.ts index ab433f56a..fe9470d18 100644 --- a/api_tests/src/image.spec.ts +++ b/api_tests/src/image.spec.ts @@ -31,14 +31,14 @@ import { waitUntil, createPostWithThumbnail, sampleImage, + sampleSite, } from "./shared"; const downloadFileSync = require("download-file-sync"); beforeAll(setupLogins); afterAll(async () => { - await unfollows(); - await deleteAllImages(alpha); + await Promise.all([unfollows(), deleteAllImages(alpha)]); }); test("Upload image and delete it", async () => { @@ -160,6 +160,7 @@ test("Purge post, linked image removed", async () => { upload.url, ); expect(post.post_view.post.url).toBe(upload.url); + expect(post.post_view.image_details).toBeDefined(); // purge post const purgeForm: PurgePost = { @@ -173,54 +174,97 @@ test("Purge post, linked image removed", async () => { expect(content2).toBe(""); }); -test("Images in remote post are proxied if setting enabled", async () => { - let user = await registerUser(beta, betaUrl); +test("Images in remote image post are proxied if setting enabled", async () => { let community = await createCommunity(gamma); - - const upload_form: UploadImage = { - image: Buffer.from("test"), - }; - const upload = await user.uploadImage(upload_form); - let post = await createPost( + let postRes = await createPost( gamma, community.community_view.community.id, - upload.url, + sampleImage, `![](${sampleImage})`, ); - expect(post.post_view.post).toBeDefined(); + const post = postRes.post_view.post; + expect(post).toBeDefined(); + + // Make sure it fetched the image details + expect(postRes.post_view.image_details).toBeDefined(); // remote image gets proxied after upload expect( - post.post_view.post.thumbnail_url?.startsWith( + post.thumbnail_url?.startsWith( "http://lemmy-gamma:8561/api/v3/image_proxy?url", ), ).toBeTruthy(); expect( - post.post_view.post.body?.startsWith( - "![](http://lemmy-gamma:8561/api/v3/image_proxy?url", - ), + post.body?.startsWith("![](http://lemmy-gamma:8561/api/v3/image_proxy?url"), ).toBeTruthy(); - let epsilonPost = await resolvePost(epsilon, post.post_view.post); - expect(epsilonPost.post).toBeDefined(); + // Make sure that it ends with jpg, to be sure its an image + expect(post.thumbnail_url?.endsWith(".jpg")).toBeTruthy(); + + let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post); + expect(epsilonPostRes.post).toBeDefined(); // Fetch the post again, the metadata should be backgrounded now // Wait for the metadata to get fetched, since this is backgrounded now - let epsilonPost2 = await waitUntil( - () => getPost(epsilon, epsilonPost.post!.post.id), + let epsilonPostRes2 = await waitUntil( + () => getPost(epsilon, epsilonPostRes.post!.post.id), p => p.post_view.post.thumbnail_url != undefined, ); + const epsilonPost = epsilonPostRes2.post_view.post; expect( - epsilonPost2.post_view.post.thumbnail_url?.startsWith( + epsilonPost.thumbnail_url?.startsWith( "http://lemmy-epsilon:8581/api/v3/image_proxy?url", ), ).toBeTruthy(); expect( - epsilonPost2.post_view.post.body?.startsWith( + epsilonPost.body?.startsWith( "![](http://lemmy-epsilon:8581/api/v3/image_proxy?url", ), ).toBeTruthy(); + + // Make sure that it ends with jpg, to be sure its an image + expect(epsilonPost.thumbnail_url?.endsWith(".jpg")).toBeTruthy(); +}); + +test("Thumbnail of remote image link is proxied if setting enabled", async () => { + let community = await createCommunity(gamma); + let postRes = await createPost( + gamma, + community.community_view.community.id, + // The sample site metadata thumbnail ends in png + sampleSite, + ); + const post = postRes.post_view.post; + expect(post).toBeDefined(); + + // remote image gets proxied after upload + expect( + post.thumbnail_url?.startsWith( + "http://lemmy-gamma:8561/api/v3/image_proxy?url", + ), + ).toBeTruthy(); + + // Make sure that it ends with png, to be sure its an image + expect(post.thumbnail_url?.endsWith(".png")).toBeTruthy(); + + let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post); + expect(epsilonPostRes.post).toBeDefined(); + + let epsilonPostRes2 = await waitUntil( + () => getPost(epsilon, epsilonPostRes.post!.post.id), + p => p.post_view.post.thumbnail_url != undefined, + ); + const epsilonPost = epsilonPostRes2.post_view.post; + + expect( + epsilonPost.thumbnail_url?.startsWith( + "http://lemmy-epsilon:8581/api/v3/image_proxy?url", + ), + ).toBeTruthy(); + + // Make sure that it ends with png, to be sure its an image + expect(epsilonPost.thumbnail_url?.endsWith(".png")).toBeTruthy(); }); test("No image proxying if setting is disabled", async () => { diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 9849f9453..fe17bd979 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -82,7 +82,7 @@ async function assertPostFederation(postOne: PostView, postTwo: PostView) { test("Create a post", async () => { // Setup some allowlists and blocklists - let editSiteForm: EditSite = {}; + const editSiteForm: EditSite = {}; editSiteForm.allowed_instances = []; editSiteForm.blocked_instances = ["lemmy-alpha"]; @@ -502,7 +502,7 @@ test("Enforce site ban federation for local user", async () => { } let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name); alphaUserHttp.setHeaders({ - Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "", + Authorization: "Bearer " + newAlphaUserJwt.jwt, }); // alpha makes new post in beta community, it federates let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index c1303743a..2ae3d9e21 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -83,21 +83,22 @@ export const fetchFunction = fetch; export const imageFetchLimit = 50; export const sampleImage = "https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg"; +export const sampleSite = "https://yahoo.com"; -export let alphaUrl = "http://127.0.0.1:8541"; -export let betaUrl = "http://127.0.0.1:8551"; -export let gammaUrl = "http://127.0.0.1:8561"; -export let deltaUrl = "http://127.0.0.1:8571"; -export let epsilonUrl = "http://127.0.0.1:8581"; +export const alphaUrl = "http://127.0.0.1:8541"; +export const betaUrl = "http://127.0.0.1:8551"; +export const gammaUrl = "http://127.0.0.1:8561"; +export const deltaUrl = "http://127.0.0.1:8571"; +export const epsilonUrl = "http://127.0.0.1:8581"; -export let alpha = new LemmyHttp(alphaUrl, { fetchFunction }); -export let alphaImage = new LemmyHttp(alphaUrl); -export let beta = new LemmyHttp(betaUrl, { fetchFunction }); -export let gamma = new LemmyHttp(gammaUrl, { fetchFunction }); -export let delta = new LemmyHttp(deltaUrl, { fetchFunction }); -export let epsilon = new LemmyHttp(epsilonUrl, { fetchFunction }); +export const alpha = new LemmyHttp(alphaUrl, { fetchFunction }); +export const alphaImage = new LemmyHttp(alphaUrl); +export const beta = new LemmyHttp(betaUrl, { fetchFunction }); +export const gamma = new LemmyHttp(gammaUrl, { fetchFunction }); +export const delta = new LemmyHttp(deltaUrl, { fetchFunction }); +export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction }); -export let betaAllowedInstances = [ +export const betaAllowedInstances = [ "lemmy-alpha", "lemmy-gamma", "lemmy-delta", @@ -363,10 +364,13 @@ export async function getUnreadCount( return api.getUnreadCount(); } -export async function getReplies(api: LemmyHttp): Promise { +export async function getReplies( + api: LemmyHttp, + unread_only: boolean = false, +): Promise { let form: GetReplies = { sort: "New", - unread_only: false, + unread_only, }; return api.getReplies(form); } @@ -896,14 +900,17 @@ export async function deleteAllImages(api: LemmyHttp) { limit: imageFetchLimit, }); imagesRes.images; - - for (const image of imagesRes.images) { - const form: DeleteImage = { - token: image.local_image.pictrs_delete_token, - filename: image.local_image.pictrs_alias, - }; - await api.deleteImage(form); - } + Promise.all( + imagesRes.images + .map(image => { + const form: DeleteImage = { + token: image.local_image.pictrs_delete_token, + filename: image.local_image.pictrs_alias, + }; + return form; + }) + .map(form => api.deleteImage(form)), + ); } export async function unfollows() { @@ -914,21 +921,20 @@ export async function unfollows() { unfollowRemotes(delta), unfollowRemotes(epsilon), ]); - await purgeAllPosts(alpha); - await purgeAllPosts(beta); - await purgeAllPosts(gamma); - await purgeAllPosts(delta); - await purgeAllPosts(epsilon); + await Promise.all([ + purgeAllPosts(alpha), + purgeAllPosts(beta), + purgeAllPosts(gamma), + purgeAllPosts(delta), + purgeAllPosts(epsilon), + ]); } export async function purgeAllPosts(api: LemmyHttp) { // The best way to get all federated items, is to find the posts let res = await api.getPosts({ type_: "All", limit: 50 }); await Promise.all( - res.posts - .map(p => p.post.id) - // Unique - .filter((v, i, a) => a.indexOf(v) == i) + Array.from(new Set(res.posts.map(p => p.post.id))) .map(post_id => api.purgePost({ post_id })) // Ignore errors .map(p => p.catch(e => e)), diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index f44f3cc0a..d008dcdc3 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -21,6 +21,7 @@ import { fetchFunction, alphaImage, unfollows, + saveUserSettingsBio, } from "./shared"; import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; @@ -186,10 +187,26 @@ test("Set a new avatar, old avatar is deleted", async () => { expect(upload2.url).toBeDefined(); let form2 = { - avatar: upload1.url, + avatar: upload2.url, }; await saveUserSettings(alpha, form2); // make sure only the new avatar is kept const listMediaRes2 = await alphaImage.listMedia(); expect(listMediaRes2.images.length).toBe(1); + + // Upload that same form2 avatar, make sure it isn't replaced / deleted + await saveUserSettings(alpha, form2); + // make sure only the new avatar is kept + const listMediaRes3 = await alphaImage.listMedia(); + expect(listMediaRes3.images.length).toBe(1); + + // Now try to save a user settings, with the icon missing, + // and make sure it doesn't clear the data, or delete the image + await saveUserSettingsBio(alpha); + let site = await getSite(alpha); + expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url); + + // make sure only the new avatar is kept + const listMediaRes4 = await alphaImage.listMedia(); + expect(listMediaRes4.images.length).toBe(1); }); diff --git a/config/defaults.hjson b/config/defaults.hjson index 1fbab1fb9..e8a70ebae 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -47,7 +47,8 @@ # # To be removed in 0.20 cache_external_link_previews: true - # Specifies how to handle remote images, so that users don't have to connect directly to remote servers. + # Specifies how to handle remote images, so that users don't have to connect directly to remote + # servers. image_mode: # Leave images unchanged, don't generate any local thumbnails for post urls. Instead the # Opengraph image is directly returned as thumbnail @@ -64,10 +65,11 @@ # or - # If enabled, all images from remote domains are rewritten to pass through `/api/v3/image_proxy`, - # including embedded images in markdown. Images are stored temporarily in pict-rs for caching. - # This improves privacy as users don't expose their IP to untrusted servers, and decreases load - # on other servers. However it increases bandwidth use for the local server. + # If enabled, all images from remote domains are rewritten to pass through + # `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily + # in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted + # servers, and decreases load on other servers. However it increases bandwidth use for the + # local server. # # Requires pict-rs 0.5 "ProxyAllImages" diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index fcbea1e5d..b98b15d62 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -33,7 +33,7 @@ anyhow = { workspace = true } tracing = { workspace = true } chrono = { workspace = true } url = { workspace = true } -wav = "1.0.0" +hound = "3.5.1" sitemap-rs = "0.2.1" totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] } actix-web-httpauth = "0.8.1" diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 93cf00415..877d9464f 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -43,7 +43,10 @@ pub async fn ban_from_community( &mut context.pool(), ) .await?; - is_valid_body_field(&data.reason, false)?; + + if let Some(reason) = &data.reason { + is_valid_body_field(reason, false)?; + } let community_user_ban_form = CommunityPersonBanForm { community_id: data.community_id, diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 4eee772c9..2b8e12d37 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -49,27 +49,33 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult { // Decode each wav file, concatenate the samples let mut concat_samples: Vec = Vec::new(); - let mut any_header: Option = None; + let mut any_header: Option = None; for letter in letters { let mut cursor = Cursor::new(letter.unwrap_or_default()); - let (header, samples) = wav::read(&mut cursor)?; - any_header = Some(header); - if let Some(samples16) = samples.as_sixteen() { - concat_samples.extend(samples16); - } else { - Err(LemmyErrorType::CouldntCreateAudioCaptcha)? - } + let reader = hound::WavReader::new(&mut cursor)?; + any_header = Some(reader.spec()); + let samples16 = reader + .into_samples::() + .collect::, _>>() + .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; + concat_samples.extend(samples16); } // Encode the concatenated result as a wav file let mut output_buffer = Cursor::new(vec![]); if let Some(header) = any_header { - wav::write( - header, - &wav::BitDepth::Sixteen(concat_samples), - &mut output_buffer, - ) - .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; + let mut writer = hound::WavWriter::new(&mut output_buffer, header) + .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; + let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32); + for sample in concat_samples { + writer16.write_sample(sample); + } + writer16 + .flush() + .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; + writer + .finalize() + .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?; Ok(base64.encode(output_buffer.into_inner())) } else { diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index c31940fba..49cd6893a 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -31,7 +31,9 @@ pub async fn ban_from_site( // Make sure user is an admin is_admin(&local_user_view)?; - is_valid_body_field(&data.reason, false)?; + if let Some(reason) = &data.reason { + is_valid_body_field(reason, false)?; + } let expires = check_expire_time(data.expires)?; diff --git a/crates/api/src/local_user/change_password_after_reset.rs b/crates/api/src/local_user/change_password_after_reset.rs index 9d693a750..f86421796 100644 --- a/crates/api/src/local_user/change_password_after_reset.rs +++ b/crates/api/src/local_user/change_password_after_reset.rs @@ -19,7 +19,7 @@ pub async fn change_password_after_reset( ) -> LemmyResult> { // Fetch the user_id from the token let token = data.token.clone(); - let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token) + let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token) .await? .ok_or(LemmyErrorType::TokenNotFound)? .local_user_id; diff --git a/crates/api/src/local_user/generate_totp_secret.rs b/crates/api/src/local_user/generate_totp_secret.rs index e8bb0284c..f2bfe7f9c 100644 --- a/crates/api/src/local_user/generate_totp_secret.rs +++ b/crates/api/src/local_user/generate_totp_secret.rs @@ -1,11 +1,7 @@ use crate::{build_totp_2fa, generate_totp_2fa_secret}; use activitypub_federation::config::Data; use actix_web::web::Json; -use lemmy_api_common::{ - context::LemmyContext, - person::GenerateTotpSecretResponse, - sensitive::Sensitive, -}; +use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse}; use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; @@ -41,6 +37,6 @@ pub async fn generate_totp_secret( .await?; Ok(Json(GenerateTotpSecretResponse { - totp_secret_url: Sensitive::new(secret_url), + totp_secret_url: secret_url.into(), })) } diff --git a/crates/api/src/local_user/notifications/unread_count.rs b/crates/api/src/local_user/notifications/unread_count.rs index 9d06f7c62..4c6c65263 100644 --- a/crates/api/src/local_user/notifications/unread_count.rs +++ b/crates/api/src/local_user/notifications/unread_count.rs @@ -11,9 +11,12 @@ pub async fn unread_count( ) -> LemmyResult> { let person_id = local_user_view.person.id; - let replies = CommentReplyView::get_unread_replies(&mut context.pool(), person_id).await?; + let replies = + CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?; - let mentions = PersonMentionView::get_unread_mentions(&mut context.pool(), person_id).await?; + let mentions = + PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user) + .await?; let private_messages = PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?; diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index edb5adee6..b6b113c07 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -6,7 +6,6 @@ use lemmy_api_common::{ utils::send_password_reset_email, SuccessResponse, }; -use lemmy_db_schema::source::password_reset_request::PasswordResetRequest; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; @@ -21,15 +20,6 @@ pub async fn reset_password( .await? .ok_or(LemmyErrorType::IncorrectLogin)?; - // Check for too many attempts (to limit potential abuse) - let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count( - &mut context.pool(), - local_user_view.local_user.id, - ) - .await?; - if recent_resets_count >= 3 { - Err(LemmyErrorType::PasswordResetLimitReached)? - } let site_view = SiteView::read_local(&mut context.pool()) .await? .ok_or(LemmyErrorType::LocalSiteNotSetup)?; diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 02b173051..193f9d269 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -21,13 +21,14 @@ use lemmy_db_schema::{ person::{Person, PersonUpdateForm}, }, traits::Crud, - utils::diesel_option_overwrite, + utils::{diesel_string_update, diesel_url_update}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ error::{LemmyErrorType, LemmyResult}, utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id}, }; +use std::ops::Deref; #[tracing::instrument(skip(context))] pub async fn save_user_settings( @@ -41,23 +42,29 @@ pub async fn save_user_settings( let slur_regex = local_site_to_slur_regex(&site_view.local_site); let url_blocklist = get_url_blocklist(&context).await?; - let bio = diesel_option_overwrite( - process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?, + let bio = diesel_string_update( + process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context) + .await? + .as_deref(), ); - replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?; - replace_image(&data.banner, &local_user_view.person.banner, &context).await?; - let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?; - let banner = proxy_image_link_opt_api(&data.banner, &context).await?; - let display_name = diesel_option_overwrite(data.display_name.clone()); - let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone()); + let avatar = diesel_url_update(data.avatar.as_deref())?; + replace_image(&avatar, &local_user_view.person.avatar, &context).await?; + let avatar = proxy_image_link_opt_api(avatar, &context).await?; + + let banner = diesel_url_update(data.banner.as_deref())?; + replace_image(&banner, &local_user_view.person.banner, &context).await?; + let banner = proxy_image_link_opt_api(banner, &context).await?; + + let display_name = diesel_string_update(data.display_name.as_deref()); + let matrix_user_id = diesel_string_update(data.matrix_user_id.as_deref()); let email_deref = data.email.as_deref().map(str::to_lowercase); - let email = diesel_option_overwrite(email_deref.clone()); + let email = diesel_string_update(email_deref.as_deref()); if let Some(Some(email)) = &email { let previous_email = local_user_view.local_user.email.clone().unwrap_or_default(); // if email was changed, check that it is not taken and send verification mail - if &previous_email != email { + if previous_email.deref() != email { if LocalUser::is_email_taken(&mut context.pool(), email).await? { return Err(LemmyErrorType::EmailAlreadyExists)?; } @@ -71,7 +78,8 @@ pub async fn save_user_settings( } } - // When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value + // When the site requires email, make sure email is not Some(None). IE, an overwrite to a None + // value if let Some(email) = &email { if email.is_none() && site_view.local_site.require_email_verification { Err(LemmyErrorType::EmailRequired)? diff --git a/crates/api/src/post/get_link_metadata.rs b/crates/api/src/post/get_link_metadata.rs index 17346790a..0669408aa 100644 --- a/crates/api/src/post/get_link_metadata.rs +++ b/crates/api/src/post/get_link_metadata.rs @@ -4,14 +4,19 @@ use lemmy_api_common::{ post::{GetSiteMetadata, GetSiteMetadataResponse}, request::fetch_link_metadata, }; -use lemmy_utils::error::LemmyResult; +use lemmy_utils::{ + error::{LemmyErrorExt, LemmyResult}, + LemmyErrorType, +}; +use url::Url; #[tracing::instrument(skip(context))] pub async fn get_link_metadata( data: Query, context: Data, ) -> LemmyResult> { - let metadata = fetch_link_metadata(&data.url, &context).await?; + let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?; + let metadata = fetch_link_metadata(&url, &context).await?; Ok(Json(GetSiteMetadataResponse { metadata })) } diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index 707874ded..fccd9f8df 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -68,7 +68,6 @@ pub async fn like_post( .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; } - // Mark the post as read mark_post_as_read(person_id, post_id, &mut context.pool()).await?; let community = Community::read(&mut context.pool(), post.community_id) diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index 0876992ad..96dd85579 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -38,7 +38,6 @@ pub async fn save_post( .await? .ok_or(LemmyErrorType::CouldntFindPost)?; - // Mark the post as read mark_post_as_read(person_id, post_id, &mut context.pool()).await?; Ok(Json(PostResponse { post_view })) diff --git a/crates/api/src/site/registration_applications/approve.rs b/crates/api/src/site/registration_applications/approve.rs index 0fb55ffc8..823af54c4 100644 --- a/crates/api/src/site/registration_applications/approve.rs +++ b/crates/api/src/site/registration_applications/approve.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm}, }, traits::Crud, - utils::diesel_option_overwrite, + utils::diesel_string_update, }; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; use lemmy_utils::{error::LemmyResult, LemmyErrorType}; @@ -26,7 +26,7 @@ pub async fn approve_registration_application( is_admin(&local_user_view)?; // Update the registration with reason, admin_id - let deny_reason = diesel_option_overwrite(data.deny_reason.clone()); + let deny_reason = diesel_string_update(data.deny_reason.as_deref()); let app_form = RegistrationApplicationUpdateForm { admin_id: Some(Some(local_user_view.person.id)), deny_reason, diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 4033b4130..25bc02893 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -66,13 +66,13 @@ actix-web = { workspace = true, optional = true } enum-map = { workspace = true } urlencoding = { workspace = true } mime = { version = "0.3.17", optional = true } -webpage = { version = "1.6", default-features = false, features = [ +webpage = { version = "2.0", default-features = false, features = [ "serde", ], optional = true } encoding_rs = { version = "0.8.34", optional = true } -jsonwebtoken = { version = "8.3.0", optional = true } +jsonwebtoken = { version = "9.3.0", optional = true } # necessary for wasmt compilation -getrandom = { version = "0.2.14", features = ["js"] } +getrandom = { version = "0.2.15", features = ["js"] } [package.metadata.cargo-machete] ignored = ["getrandom"] diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index f77a90882..85cf065eb 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -121,7 +121,8 @@ pub async fn send_local_notifs( if let Ok(Some(mention_user_view)) = user_view { // TODO // At some point, make it so you can't tag the parent creator either - // Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids + // Potential duplication of notifications, one for reply and the other for mention, is handled + // below by checking recipient ids recipient_ids.push(mention_user_view.local_user.id); let user_mention_form = PersonMentionInsertForm { diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index a926cb0ba..6c17d4e6a 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -1,9 +1,10 @@ -use crate::{context::LemmyContext, sensitive::Sensitive}; +use crate::context::LemmyContext; use actix_web::{http::header::USER_AGENT, HttpRequest}; use chrono::Utc; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use lemmy_db_schema::{ newtypes::LocalUserId, + sensitive::SensitiveString, source::login_token::{LoginToken, LoginTokenCreateForm}, }; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; @@ -40,7 +41,7 @@ impl Claims { user_id: LocalUserId, req: HttpRequest, context: &LemmyContext, - ) -> LemmyResult> { + ) -> LemmyResult { let hostname = context.settings().hostname.clone(); let my_claims = Claims { sub: user_id.0.to_string(), @@ -50,7 +51,7 @@ impl Claims { let secret = &context.secret().jwt_secret; let key = EncodingKey::from_secret(secret.as_ref()); - let token = encode(&Header::default(), &my_claims, &key)?; + let token: SensitiveString = encode(&Header::default(), &my_claims, &key)?.into(); let ip = req .connection_info() .realip_remote_addr() @@ -67,7 +68,7 @@ impl Claims { user_agent, }; LoginToken::create(&mut context.pool(), form).await?; - Ok(Sensitive::new(token)) + Ok(token) } } @@ -111,11 +112,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("Gerry9812".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/api_common/src/context.rs b/crates/api_common/src/context.rs index ba9eb4074..f4ac41db1 100644 --- a/crates/api_common/src/context.rs +++ b/crates/api_common/src/context.rs @@ -64,7 +64,7 @@ impl LemmyContext { let client = ClientBuilder::new(client).build(); let secret = Secret { id: 0, - jwt_secret: String::new(), + jwt_secret: String::new().into(), }; let rate_limit_cell = RateLimitCell::with_test_config(); diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 4bb7ada5b..9d12d2e13 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -14,7 +14,6 @@ pub mod private_message; pub mod request; #[cfg(feature = "full")] pub mod send_activity; -pub mod sensitive; pub mod site; #[cfg(feature = "full")] pub mod utils; diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 6f020df6d..f61f784c2 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -1,6 +1,6 @@ -use crate::sensitive::Sensitive; use lemmy_db_schema::{ newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId}, + sensitive::SensitiveString, source::site::Site, CommentSortType, ListingType, @@ -25,8 +25,8 @@ use ts_rs::TS; #[cfg_attr(feature = "full", ts(export))] /// Logging into lemmy. pub struct Login { - pub username_or_email: Sensitive, - pub password: Sensitive, + pub username_or_email: SensitiveString, + pub password: SensitiveString, /// May be required, if totp is enabled for their account. pub totp_2fa_token: Option, } @@ -38,11 +38,11 @@ pub struct Login { /// Register / Sign up to lemmy. pub struct Register { pub username: String, - pub password: Sensitive, - pub password_verify: Sensitive, + pub password: SensitiveString, + pub password_verify: SensitiveString, pub show_nsfw: Option, /// email is mandatory if email verification is enabled on the server - pub email: Option>, + pub email: Option, /// The UUID of the captcha item. pub captcha_uuid: Option, /// Your captcha answer. @@ -99,7 +99,7 @@ pub struct SaveUserSettings { /// Your display name, which can contain strange characters, and does not need to be unique. pub display_name: Option, /// Your email. - pub email: Option>, + pub email: Option, /// Your bio / info, in markdown. pub bio: Option, /// Your matrix user id. Ex: @my_user:matrix.org @@ -124,7 +124,8 @@ pub struct SaveUserSettings { pub post_listing_mode: Option, /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). pub enable_keyboard_navigation: Option, - /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or should be paused + /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or + /// should be paused pub enable_animated_images: Option, /// Whether to auto-collapse bot comments. pub collapse_bot_comments: Option, @@ -140,9 +141,9 @@ pub struct SaveUserSettings { #[cfg_attr(feature = "full", ts(export))] /// Changes your account password. pub struct ChangePassword { - pub new_password: Sensitive, - pub new_password_verify: Sensitive, - pub old_password: Sensitive, + pub new_password: SensitiveString, + pub new_password_verify: SensitiveString, + pub old_password: SensitiveString, } #[skip_serializing_none] @@ -151,8 +152,9 @@ pub struct ChangePassword { #[cfg_attr(feature = "full", ts(export))] /// A response for your login. pub struct LoginResponse { - /// This is None in response to `Register` if email verification is enabled, or the server requires registration applications. - pub jwt: Option>, + /// This is None in response to `Register` if email verification is enabled, or the server + /// requires registration applications. + pub jwt: Option, /// If registration applications are required, this will return true for a signup response. pub registration_created: bool, /// If email verifications are required, this will return true for a signup response. @@ -340,7 +342,7 @@ pub struct CommentReplyResponse { #[cfg_attr(feature = "full", ts(export))] /// Delete your account. pub struct DeleteAccount { - pub password: Sensitive, + pub password: SensitiveString, pub delete_content: bool, } @@ -349,7 +351,7 @@ pub struct DeleteAccount { #[cfg_attr(feature = "full", ts(export))] /// Reset your password via email. pub struct PasswordReset { - pub email: Sensitive, + pub email: SensitiveString, } #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] @@ -357,9 +359,9 @@ pub struct PasswordReset { #[cfg_attr(feature = "full", ts(export))] /// Change your password after receiving a reset request. pub struct PasswordChangeAfterReset { - pub token: Sensitive, - pub password: Sensitive, - pub password_verify: Sensitive, + pub token: SensitiveString, + pub password: SensitiveString, + pub password_verify: SensitiveString, } #[skip_serializing_none] @@ -405,7 +407,7 @@ pub struct VerifyEmail { #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] pub struct GenerateTotpSecretResponse { - pub totp_secret_url: Sensitive, + pub totp_secret_url: SensitiveString, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 49327dac1..3d1bc4078 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] use ts_rs::TS; -use url::Url; #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] @@ -20,8 +19,7 @@ use url::Url; pub struct CreatePost { pub name: String, pub community_id: CommunityId, - #[cfg_attr(feature = "full", ts(type = "string"))] - pub url: Option, + pub url: Option, /// An optional body for the post in markdown. pub body: Option, /// An optional alt_text, usable for image posts. @@ -30,9 +28,8 @@ pub struct CreatePost { pub honeypot: Option, pub nsfw: Option, pub language_id: Option, - #[cfg_attr(feature = "full", ts(type = "string"))] /// Instead of fetching a thumbnail, use a custom one. - pub custom_thumbnail: Option, + pub custom_thumbnail: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -114,17 +111,15 @@ pub struct CreatePostLike { pub struct EditPost { pub post_id: PostId, pub name: Option, - #[cfg_attr(feature = "full", ts(type = "string"))] - pub url: Option, + pub url: Option, /// An optional body for the post in markdown. pub body: Option, /// An optional alt_text, usable for image posts. pub alt_text: Option, pub nsfw: Option, pub language_id: Option, - #[cfg_attr(feature = "full", ts(type = "string"))] /// Instead of fetching a thumbnail, use a custom one. - pub custom_thumbnail: Option, + pub custom_thumbnail: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] @@ -249,8 +244,7 @@ pub struct ListPostReportsResponse { #[cfg_attr(feature = "full", ts(export))] /// Get metadata for a given site. pub struct GetSiteMetadata { - #[cfg_attr(feature = "full", ts(type = "string"))] - pub url: Url, + pub url: String, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index 6c0ea5fe4..ddbb3dd0c 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -93,35 +93,21 @@ pub async fn generate_post_link_metadata( let allow_sensitive = local_site_opt_to_sensitive(&local_site); let allow_generate_thumbnail = allow_sensitive || !post.nsfw; - let thumbnail_url = if is_image_post { - if allow_generate_thumbnail { - match post.url { - Some(url) => generate_pictrs_thumbnail(&url, &context) - .await - .ok() - .map(Into::into), - None => None, - } - } else { - None - } + let image_url = if is_image_post { + post.url } else { - // Use custom thumbnail if available and its not an image post - if let Some(custom_thumbnail) = custom_thumbnail { - proxy_image_link(custom_thumbnail, &context).await.ok() - } else if allow_generate_thumbnail { - match metadata.opengraph_data.image { - Some(url) => generate_pictrs_thumbnail(&url, &context) - .await - .ok() - .map(Into::into), - None => None, - } - } - // Otherwise use opengraph preview image directly - else { - metadata.opengraph_data.image - } + metadata.opengraph_data.image.clone() + }; + + let thumbnail_url = if let (false, Some(url)) = (is_image_post, custom_thumbnail) { + proxy_image_link(url, &context).await.ok() + } else if let (true, Some(url)) = (allow_generate_thumbnail, image_url) { + generate_pictrs_thumbnail(&url, &context) + .await + .ok() + .map(Into::into) + } else { + metadata.opengraph_data.image.clone() }; let form = PostUpdateForm { @@ -212,9 +198,12 @@ impl PictrsFile { } } +/// Stores extra details about a Pictrs image. #[derive(Deserialize, Serialize, Debug)] pub struct PictrsFileDetails { + /// In pixels pub width: u16, + /// In pixels pub height: u16, pub content_type: String, pub created_at: DateTime, @@ -315,34 +304,36 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L encode(image_url.as_str()) ); - let res: PictrsResponse = context + let res = context .client() .get(&fetch_url) .timeout(REQWEST_TIMEOUT) .send() .await? - .json() + .json::() .await?; - if let Some(image) = res.files.unwrap_or_default().first() { - let form = LocalImageForm { - // This is none because its an internal request. - // IE, a local user shouldn't get to delete the thumbnails for their link posts - local_user_id: None, - pictrs_alias: image.file.clone(), - pictrs_delete_token: image.delete_token.clone(), - }; - let protocol_and_hostname = context.settings().get_protocol_and_hostname(); - let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?; + let files = res.files.unwrap_or_default(); - // Also store the details for the image - let details_form = image.details.build_image_details_form(&thumbnail_url); - LocalImage::create(&mut context.pool(), &form, &details_form).await?; + let image = files + .first() + .ok_or(LemmyErrorType::PictrsResponseError(res.msg))?; - Ok(thumbnail_url) - } else { - Err(LemmyErrorType::PictrsResponseError(res.msg))? - } + let form = LocalImageForm { + // This is none because its an internal request. + // IE, a local user shouldn't get to delete the thumbnails for their link posts + local_user_id: None, + pictrs_alias: image.file.clone(), + pictrs_delete_token: image.delete_token.clone(), + }; + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); + let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?; + + // Also store the details for the image + let details_form = image.details.build_image_details_form(&thumbnail_url); + LocalImage::create(&mut context.pool(), &form, &details_form).await?; + + Ok(thumbnail_url) } /// Fetches the image details for pictrs proxied images @@ -353,25 +344,24 @@ pub async fn fetch_pictrs_proxied_image_details( image_url: &Url, context: &LemmyContext, ) -> LemmyResult { - let pictrs_config = context.settings().pictrs_config()?; + let pictrs_url = context.settings().pictrs_config()?.url; + let encoded_image_url = encode(image_url.as_str()); // Pictrs needs you to fetch the proxied image before you can fetch the details - let proxy_url = format!( - "{}image/original?proxy={}", - context.settings().pictrs_config()?.url, - encode(image_url.as_str()) - ); + let proxy_url = format!("{pictrs_url}image/original?proxy={encoded_image_url}"); - let res = context.client().get(&proxy_url).send().await?.status(); + let res = context + .client() + .get(&proxy_url) + .timeout(REQWEST_TIMEOUT) + .send() + .await? + .status(); if !res.is_success() { Err(LemmyErrorType::NotAnImageType)? } - let details_url = format!( - "{}image/details/original?proxy={}", - pictrs_config.url, - encode(image_url.as_str()) - ); + let details_url = format!("{pictrs_url}image/details/original?proxy={encoded_image_url}"); let res = context .client() @@ -402,16 +392,19 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Lemm } } -/// When adding a new avatar or similar image, delete the old one. +/// When adding a new avatar, banner or similar image, delete the old one. pub async fn replace_image( - new_image: &Option, + new_image: &Option>, old_image: &Option, context: &Data, ) -> LemmyResult<()> { - if new_image.is_some() { - // Ignore errors because image may be stored externally. - if let Some(avatar) = &old_image { - let image = LocalImage::delete_by_url(&mut context.pool(), avatar) + if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) { + // Note: Oftentimes front ends will include the current image in the form. + // In this case, deleting `old_image` would also be deletion of `new_image`, + // so the deletion must be skipped for the image to be kept. + if new_image != old_image { + // Ignore errors because image may be stored externally. + let image = LocalImage::delete_by_url(&mut context.pool(), old_image) .await .ok(); if let Some(image) = image { diff --git a/crates/api_common/src/sensitive.rs b/crates/api_common/src/sensitive.rs deleted file mode 100644 index 4dd120805..000000000 --- a/crates/api_common/src/sensitive.rs +++ /dev/null @@ -1,116 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{ - borrow::Borrow, - ops::{Deref, DerefMut}, -}; -#[cfg(feature = "full")] -use ts_rs::TS; - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Default)] -#[serde(transparent)] -pub struct Sensitive(T); - -impl Sensitive { - pub fn new(item: T) -> Self { - Sensitive(item) - } - pub fn into_inner(self) -> T { - self.0 - } -} - -impl std::fmt::Debug for Sensitive { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Sensitive").finish() - } -} - -impl AsRef for Sensitive { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl AsRef for Sensitive { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl AsRef<[u8]> for Sensitive { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsRef<[u8]> for Sensitive> { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl AsMut for Sensitive { - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl AsMut for Sensitive { - fn as_mut(&mut self) -> &mut str { - &mut self.0 - } -} - -impl Deref for Sensitive { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Sensitive { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for Sensitive { - fn from(t: T) -> Self { - Sensitive(t) - } -} - -impl From<&str> for Sensitive { - fn from(s: &str) -> Self { - Sensitive(s.into()) - } -} - -impl Borrow for Sensitive { - fn borrow(&self) -> &T { - &self.0 - } -} - -impl Borrow for Sensitive { - fn borrow(&self) -> &str { - &self.0 - } -} - -#[cfg(feature = "full")] -impl TS for Sensitive { - fn name() -> String { - "string".to_string() - } - fn name_with_type_args(_args: Vec) -> String { - "string".to_string() - } - fn dependencies() -> Vec { - Vec::new() - } - fn transparent() -> bool { - true - } -} diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index d87cbdaaf..77bfcd8ee 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -375,7 +375,8 @@ impl From for ReadableFederationState { pub struct InstanceWithFederationState { #[serde(flatten)] pub instance: Instance, - /// if federation to this instance is or was active, show state of outgoing federation to this instance + /// if federation to this instance is or was active, show state of outgoing federation to this + /// instance pub federation_state: Option, } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 25a554927..97b12cc5b 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -10,6 +10,7 @@ use crate::{ use chrono::{DateTime, Days, Local, TimeZone, Utc}; use enum_map::{enum_map, EnumMap}; use lemmy_db_schema::{ + aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId}, source::{ comment::{Comment, CommentUpdateForm}, @@ -143,13 +144,7 @@ pub fn is_top_mod( } } -#[tracing::instrument(skip_all)] -pub async fn get_post(post_id: PostId, pool: &mut DbPool<'_>) -> LemmyResult { - Post::read(pool, post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost.into()) -} - +/// Marks a post as read for a given person. #[tracing::instrument(skip_all)] pub async fn mark_post_as_read( person_id: PersonId, @@ -162,6 +157,28 @@ pub async fn mark_post_as_read( Ok(()) } +/// Updates the read comment count for a post. Usually done when reading or creating a new comment. +#[tracing::instrument(skip_all)] +pub async fn update_read_comments( + person_id: PersonId, + post_id: PostId, + read_comments: i64, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + let person_post_agg_form = PersonPostAggregatesForm { + person_id, + post_id, + read_comments, + ..PersonPostAggregatesForm::default() + }; + + PersonPostAggregates::upsert(pool, &person_post_agg_form) + .await + .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; + + Ok(()) +} + pub fn check_user_valid(person: &Person) -> LemmyResult<()> { // Check for a site ban if person.banned { @@ -360,7 +377,8 @@ pub async fn build_federated_instances( federation_state: federation_state.map(std::convert::Into::into), }; if is_blocked { - // blocked instances will only have an entry here if they had been federated with in the past. + // blocked instances will only have an entry here if they had been federated with in the + // past. blocked.push(i); } else if is_allowed { allowed.push(i.clone()); @@ -444,7 +462,7 @@ pub async fn send_password_reset_email( // Insert the row after successful send, to avoid using daily reset limit while // email sending is broken. let local_user_id = user.local_user.id; - PasswordResetRequest::create_token(pool, local_user_id, token.clone()).await?; + PasswordResetRequest::create(pool, local_user_id, token.clone()).await?; Ok(()) } @@ -969,8 +987,8 @@ pub async fn process_markdown_opt( /// A wrapper for `proxy_image_link` for use in tests. /// -/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to pass -/// as separate parameter so it can be changed in tests. +/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to +/// pass as separate parameter so it can be changed in tests. async fn proxy_image_link_internal( link: Url, image_mode: PictrsImageMode, @@ -981,14 +999,13 @@ async fn proxy_image_link_internal( Ok(link.into()) } else if image_mode == PictrsImageMode::ProxyAllImages { let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?; - // This should fail softly, since pictrs might not even be running let details_res = fetch_pictrs_proxied_image_details(&link, context).await; if let Ok(details) = details_res { let details_form = details.build_image_details_form(&proxied); RemoteImage::create(&mut context.pool(), &details_form).await?; - } + }; Ok(proxied.into()) } else { @@ -1008,26 +1025,25 @@ pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> Lemmy } pub async fn proxy_image_link_opt_api( - link: &Option, + link: Option>, context: &LemmyContext, ) -> LemmyResult>> { - proxy_image_link_api(link, context).await.map(Some) + if let Some(Some(link)) = link { + proxy_image_link(link.into(), context) + .await + .map(Some) + .map(Some) + } else { + Ok(link) + } } pub async fn proxy_image_link_api( - link: &Option, + link: Option, context: &LemmyContext, ) -> LemmyResult> { - let link: Option = match link.as_ref().map(String::as_str) { - // An empty string is an erase - Some("") => None, - Some(str_url) => Url::parse(str_url) - .map(|u| Some(u.into())) - .with_lemmy_type(LemmyErrorType::InvalidUrl)?, - None => None, - }; - if let Some(l) = link { - proxy_image_link(l.into(), context).await.map(Some) + if let Some(link) = link { + proxy_image_link(link.into(), context).await.map(Some) } else { Ok(link) } @@ -1137,29 +1153,4 @@ mod tests { .is_err() ); } - - #[tokio::test] - #[serial] - async fn test_diesel_option_overwrite_to_url() { - let context = LemmyContext::init_test_context().await; - - assert!(matches!( - proxy_image_link_api(&None, &context).await, - Ok(None) - )); - assert!(matches!( - proxy_image_link_opt_api(&Some(String::new()), &context).await, - Ok(Some(None)) - )); - assert!( - proxy_image_link_opt_api(&Some("invalid_url".to_string()), &context) - .await - .is_err() - ); - let example_url = "https://lemmy-alpha/image.png"; - assert!(matches!( - proxy_image_link_opt_api(&Some(example_url.to_string()), &context).await, - Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into() - )); - } } diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 6493d6803..636f83392 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -9,11 +9,11 @@ use lemmy_api_common::{ check_community_user_action, check_post_deleted_or_removed, generate_local_apub_endpoint, - get_post, get_url_blocklist, is_mod_or_admin, local_site_to_slur_regex, process_markdown, + update_read_comments, EndpointType, }, }; @@ -28,7 +28,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views::structs::{LocalUserView, PostView}; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field}, @@ -47,12 +47,23 @@ pub async fn create_comment( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; - is_valid_body_field(&Some(content.clone()), false)?; + is_valid_body_field(&content, false)?; // Check for a community ban let post_id = data.post_id; - let post = get_post(post_id, &mut context.pool()).await?; - let community_id = post.community_id; + + // Read the full post view in order to get the comments count. + let post_view = PostView::read( + &mut context.pool(), + post_id, + Some(local_user_view.person.id), + true, + ) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; + + let post = post_view.post; + let community_id = post_view.community.id; check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; check_post_deleted_or_removed(&post)?; @@ -164,6 +175,15 @@ pub async fn create_comment( ) .await?; + // Update the read comments, so your own new comment doesn't appear as a +1 unread + update_read_comments( + local_user_view.person.id, + post_id, + post_view.counts.comments + 1, + &mut context.pool(), + ) + .await?; + // If we're responding to a comment where we're the recipient, // (ie we're the grandparent, or the recipient of the parent comment_reply), // then mark the parent as read. diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index 02ae7b9fd..926472b94 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -37,6 +37,12 @@ pub async fn remove_comment( ) .await?; + // Don't allow removing or restoring comment which was deleted by user, as it would reveal + // the comment text in mod log. + if orig_comment.comment.deleted { + return Err(LemmyErrorType::CouldntUpdateComment.into()); + } + // Do the remove let removed = data.removed; let updated_comment = Comment::update( diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 695ededfe..4c8cf9436 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -63,7 +63,9 @@ pub async fn update_comment( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; let content = process_markdown_opt(&data.content, &slur_regex, &url_blocklist, &context).await?; - is_valid_body_field(&content, false)?; + if let Some(content) = &content { + is_valid_body_field(content, false)?; + } let comment_id = data.comment_id; let form = CommentUpdateForm { diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index b0b6bea0e..4289b7d24 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -30,6 +30,7 @@ use lemmy_db_schema::{ }, }, traits::{ApubActor, Crud, Followable, Joinable}, + utils::diesel_url_create, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ @@ -61,11 +62,18 @@ pub async fn create_community( check_slurs(&data.title, &slur_regex)?; let description = process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?; - let icon = proxy_image_link_api(&data.icon, &context).await?; - let banner = proxy_image_link_api(&data.banner, &context).await?; + + let icon = diesel_url_create(data.icon.as_deref())?; + let icon = proxy_image_link_api(icon, &context).await?; + + let banner = diesel_url_create(data.banner.as_deref())?; + let banner = proxy_image_link_api(banner, &context).await?; is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; - is_valid_body_field(&data.description, false)?; + + if let Some(desc) = &data.description { + is_valid_body_field(desc, false)?; + } // Double check for duplicate community actor_ids let community_actor_id = generate_local_apub_endpoint( diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 33c6a47dd..6190a0ca7 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -21,7 +21,7 @@ use lemmy_db_schema::{ local_site::LocalSite, }, traits::Crud, - utils::{diesel_option_overwrite, naive_now}, + utils::{diesel_string_update, diesel_url_update, naive_now}, }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ @@ -40,18 +40,28 @@ pub async fn update_community( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; check_slurs_opt(&data.title, &slur_regex)?; - let description = - process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?; - is_valid_body_field(&data.description, false)?; + + let description = diesel_string_update( + process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context) + .await? + .as_deref(), + ); + + if let Some(Some(desc)) = &description { + is_valid_body_field(desc, false)?; + } + let old_community = Community::read(&mut context.pool(), data.community_id) .await? .ok_or(LemmyErrorType::CouldntFindCommunity)?; - replace_image(&data.icon, &old_community.icon, &context).await?; - replace_image(&data.banner, &old_community.banner, &context).await?; - let description = diesel_option_overwrite(description); - let icon = proxy_image_link_opt_api(&data.icon, &context).await?; - let banner = proxy_image_link_opt_api(&data.banner, &context).await?; + let icon = diesel_url_update(data.icon.as_deref())?; + replace_image(&icon, &old_community.icon, &context).await?; + let icon = proxy_image_link_opt_api(icon, &context).await?; + + let banner = diesel_url_update(data.banner.as_deref())?; + replace_image(&banner, &old_community.banner, &context).await?; + let banner = proxy_image_link_opt_api(banner, &context).await?; // Verify its a mod (only mods can edit it) check_community_mod_action( diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 82a208a9c..0b0fad5dc 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -26,6 +26,7 @@ use lemmy_db_schema::{ post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm}, }, traits::{Crud, Likeable}, + utils::diesel_url_create, CommunityVisibility, }; use lemmy_db_views::structs::LocalUserView; @@ -37,7 +38,6 @@ use lemmy_utils::{ slurs::check_slurs, validation::{ check_url_scheme, - clean_url_params, is_url_blocked, is_valid_alt_text_field, is_valid_body_field, @@ -64,16 +64,27 @@ pub async fn create_post( let url_blocklist = get_url_blocklist(&context).await?; let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?; - let data_url = data.url.as_ref(); - let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear" - let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params); + let url = diesel_url_create(data.url.as_deref())?; + let custom_thumbnail = diesel_url_create(data.custom_thumbnail.as_deref())?; is_valid_post_title(&data.name)?; - is_valid_body_field(&body, true)?; - is_valid_alt_text_field(&data.alt_text)?; - is_url_blocked(&url, &url_blocklist)?; - check_url_scheme(&url)?; - check_url_scheme(&custom_thumbnail)?; + + if let Some(url) = &url { + is_url_blocked(url, &url_blocklist)?; + check_url_scheme(url)?; + } + + if let Some(custom_thumbnail) = &custom_thumbnail { + check_url_scheme(custom_thumbnail)?; + } + + if let Some(alt_text) = &data.alt_text { + is_valid_alt_text_field(alt_text)?; + } + + if let Some(body) = &body { + is_valid_body_field(body, true)?; + } check_community_user_action( &local_user_view.person, @@ -156,7 +167,7 @@ pub async fn create_post( generate_post_link_metadata( updated_post.clone(), - custom_thumbnail, + custom_thumbnail.map(Into::into), |post| Some(SendActivityData::CreatePost(post)), Some(local_site), context.reset_request_count(), @@ -176,7 +187,6 @@ pub async fn create_post( .await .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; - // Mark the post as read mark_post_as_read(person_id, post_id, &mut context.pool()).await?; if let Some(url) = updated_post.url.clone() { diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index 2a567187f..60b5609a9 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -2,10 +2,9 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPost, GetPostResponse}, - utils::{check_private_instance, is_mod_or_admin_opt, mark_post_as_read}, + utils::{check_private_instance, is_mod_or_admin_opt, mark_post_as_read, update_read_comments}, }; use lemmy_db_schema::{ - aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, source::{comment::Comment, post::Post}, traits::Crud, }; @@ -14,7 +13,7 @@ use lemmy_db_views::{ structs::{LocalUserView, PostView, SiteView}, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn get_post( @@ -60,10 +59,17 @@ pub async fn get_post( .await? .ok_or(LemmyErrorType::CouldntFindPost)?; - // Mark the post as read let post_id = post_view.post.id; if let Some(person_id) = person_id { mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + + update_read_comments( + person_id, + post_id, + post_view.counts.comments, + &mut context.pool(), + ) + .await?; } // Necessary for the sidebar subscribed @@ -76,27 +82,14 @@ pub async fn get_post( .await? .ok_or(LemmyErrorType::CouldntFindCommunity)?; - // Insert into PersonPostAggregates - // to update the read_comments count - if let Some(person_id) = person_id { - let read_comments = post_view.counts.comments; - let person_post_agg_form = PersonPostAggregatesForm { - person_id, - post_id, - read_comments, - ..PersonPostAggregatesForm::default() - }; - PersonPostAggregates::upsert(&mut context.pool(), &person_post_agg_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; - } - let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + let local_user = local_user_view.as_ref().map(|u| &u.local_user); // Fetch the cross_posts let cross_posts = if let Some(url) = &post_view.post.url { let mut x_posts = PostQuery { url_search: Some(url.inner().as_str().into()), + local_user, ..Default::default() } .list(&local_site.site, &mut context.pool()) diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 4b4bd9845..9e665aed6 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -20,16 +20,15 @@ use lemmy_db_schema::{ post::{Post, PostUpdateForm}, }, traits::Crud, - utils::{diesel_option_overwrite, naive_now}, + utils::{diesel_string_update, diesel_url_update, naive_now}, }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ - slurs::check_slurs_opt, + slurs::check_slurs, validation::{ check_url_scheme, - clean_url_params, is_url_blocked, is_valid_alt_text_field, is_valid_body_field, @@ -47,26 +46,43 @@ pub async fn update_post( ) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; - // TODO No good way to handle a clear. - // Issue link: https://github.com/LemmyNet/lemmy/issues/2287 - let url = data.url.as_ref().map(clean_url_params); - let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params); + let url = diesel_url_update(data.url.as_deref())?; + + let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?; let url_blocklist = get_url_blocklist(&context).await?; let slur_regex = local_site_to_slur_regex(&local_site); - check_slurs_opt(&data.name, &slur_regex)?; - let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?; + + let body = diesel_string_update( + process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context) + .await? + .as_deref(), + ); + + let alt_text = diesel_string_update(data.alt_text.as_deref()); if let Some(name) = &data.name { is_valid_post_title(name)?; + check_slurs(name, &slur_regex)?; } - is_valid_body_field(&body, true)?; - is_valid_alt_text_field(&data.alt_text)?; - is_url_blocked(&url, &url_blocklist)?; - check_url_scheme(&url)?; - check_url_scheme(&custom_thumbnail)?; + if let Some(Some(body)) = &body { + is_valid_body_field(body, true)?; + } + + if let Some(Some(alt_text)) = &alt_text { + is_valid_alt_text_field(alt_text)?; + } + + if let Some(Some(url)) = &url { + is_url_blocked(url, &url_blocklist)?; + check_url_scheme(url)?; + } + + if let Some(Some(custom_thumbnail)) = &custom_thumbnail { + check_url_scheme(custom_thumbnail)?; + } let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id) @@ -95,9 +111,9 @@ pub async fn update_post( let post_form = PostUpdateForm { name: data.name.clone(), - url: Some(url.map(Into::into)), - body: diesel_option_overwrite(body), - alt_text: diesel_option_overwrite(data.alt_text.clone()), + url, + body, + alt_text, nsfw: data.nsfw, language_id: data.language_id, updated: Some(Some(naive_now())), @@ -111,7 +127,7 @@ pub async fn update_post( generate_post_link_metadata( updated_post.clone(), - custom_thumbnail, + custom_thumbnail.flatten().map(Into::into), |post| Some(SendActivityData::UpdatePost(post)), Some(local_site), context.reset_request_count(), diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index e977a6c86..0381d196c 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -39,7 +39,7 @@ pub async fn create_private_message( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; - is_valid_body_field(&Some(content.clone()), false)?; + is_valid_body_field(&content, false)?; check_person_block( local_user_view.person.id, diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 2842fea65..364d5c2e3 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -41,7 +41,7 @@ pub async fn update_private_message( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; - is_valid_body_field(&Some(content.clone()), false)?; + is_valid_body_field(&content, false)?; let private_message_id = data.private_message_id; PrivateMessage::update( diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 466c7ff1d..6b1909966 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -11,7 +11,7 @@ use lemmy_api_common::{ local_site_rate_limit_to_rate_limit_config, local_site_to_slur_regex, process_markdown_opt, - proxy_image_link_opt_api, + proxy_image_link_api, }, }; use lemmy_db_schema::{ @@ -23,7 +23,7 @@ use lemmy_db_schema::{ tagline::Tagline, }, traits::Crud, - utils::{diesel_option_overwrite, naive_now}, + utils::{diesel_string_update, diesel_url_create, naive_now}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ @@ -61,21 +61,25 @@ pub async fn create_site( let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?; - let icon = proxy_image_link_opt_api(&data.icon, &context).await?; - let banner = proxy_image_link_opt_api(&data.banner, &context).await?; + + let icon = diesel_url_create(data.icon.as_deref())?; + let icon = proxy_image_link_api(icon, &context).await?; + + let banner = diesel_url_create(data.banner.as_deref())?; + let banner = proxy_image_link_api(banner, &context).await?; let site_form = SiteUpdateForm { name: Some(data.name.clone()), - sidebar: diesel_option_overwrite(sidebar), - description: diesel_option_overwrite(data.description.clone()), - icon, - banner, + sidebar: diesel_string_update(sidebar.as_deref()), + description: diesel_string_update(data.description.as_deref()), + icon: Some(icon), + banner: Some(banner), actor_id: Some(actor_id), last_refreshed_at: Some(naive_now()), inbox_url, private_key: Some(Some(keypair.private_key)), public_key: Some(keypair.public_key), - content_warning: diesel_option_overwrite(data.content_warning.clone()), + content_warning: diesel_string_update(data.content_warning.as_deref()), ..Default::default() }; @@ -91,16 +95,16 @@ pub async fn create_site( enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, - application_question: diesel_option_overwrite(data.application_question.clone()), + application_question: diesel_string_update(data.application_question.as_deref()), private_instance: data.private_instance, default_theme: data.default_theme.clone(), default_post_listing_type: data.default_post_listing_type, default_sort_type: data.default_sort_type, - legal_information: diesel_option_overwrite(data.legal_information.clone()), + legal_information: diesel_string_update(data.legal_information.as_deref()), application_email_admins: data.application_email_admins, hide_modlog_mod_names: data.hide_modlog_mod_names, updated: Some(Some(naive_now())), - slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()), + slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()), actor_name_max_length: data.actor_name_max_length, federation_enabled: data.federation_enabled, captcha_enabled: data.captcha_enabled, @@ -179,7 +183,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> )?; // Ensure that the sidebar has fewer than the max num characters... - is_valid_body_field(&create_site.sidebar, false)?; + if let Some(body) = &create_site.sidebar { + is_valid_body_field(body, false)?; + } application_question_check( &local_site.application_question, diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 7b44b92a6..f6377038d 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ tagline::Tagline, }, traits::Crud, - utils::{diesel_option_overwrite, naive_now}, + utils::{diesel_string_update, diesel_url_update, naive_now}, RegistrationMode, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; @@ -67,22 +67,29 @@ pub async fn update_site( SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?; } - replace_image(&data.icon, &site.icon, &context).await?; - replace_image(&data.banner, &site.banner, &context).await?; - let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; - let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?; - let icon = proxy_image_link_opt_api(&data.icon, &context).await?; - let banner = proxy_image_link_opt_api(&data.banner, &context).await?; + let sidebar = diesel_string_update( + process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context) + .await? + .as_deref(), + ); + + let icon = diesel_url_update(data.icon.as_deref())?; + replace_image(&icon, &site.icon, &context).await?; + let icon = proxy_image_link_opt_api(icon, &context).await?; + + let banner = diesel_url_update(data.banner.as_deref())?; + replace_image(&banner, &site.banner, &context).await?; + let banner = proxy_image_link_opt_api(banner, &context).await?; let site_form = SiteUpdateForm { name: data.name.clone(), - sidebar: diesel_option_overwrite(sidebar), - description: diesel_option_overwrite(data.description.clone()), + sidebar, + description: diesel_string_update(data.description.as_deref()), icon, banner, - content_warning: diesel_option_overwrite(data.content_warning.clone()), + content_warning: diesel_string_update(data.content_warning.as_deref()), updated: Some(Some(naive_now())), ..Default::default() }; @@ -99,16 +106,16 @@ pub async fn update_site( enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, - application_question: diesel_option_overwrite(data.application_question.clone()), + application_question: diesel_string_update(data.application_question.as_deref()), private_instance: data.private_instance, default_theme: data.default_theme.clone(), default_post_listing_type: data.default_post_listing_type, default_sort_type: data.default_sort_type, - legal_information: diesel_option_overwrite(data.legal_information.clone()), + legal_information: diesel_string_update(data.legal_information.as_deref()), application_email_admins: data.application_email_admins, hide_modlog_mod_names: data.hide_modlog_mod_names, updated: Some(Some(naive_now())), - slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()), + slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()), actor_name_max_length: data.actor_name_max_length, federation_enabled: data.federation_enabled, captcha_enabled: data.captcha_enabled, @@ -156,7 +163,8 @@ pub async fn update_site( // TODO can't think of a better way to do this. // If the server suddenly requires email verification, or required applications, no old users // will be able to log in. It really only wants this to be a requirement for NEW signups. - // So if it was set from false, to true, you need to update all current users columns to be verified. + // So if it was set from false, to true, you need to update all current users columns to be + // verified. let old_require_application = local_site.registration_mode == RegistrationMode::RequireApplication; @@ -228,7 +236,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm )?; // Ensure that the sidebar has fewer than the max num characters... - is_valid_body_field(&edit_site.sidebar, false)?; + if let Some(body) = &edit_site.sidebar { + is_valid_body_field(body, false)?; + } application_question_check( &local_site.application_question, diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 206ce241b..c84bd0a50 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -112,15 +112,17 @@ pub async fn register( // We have to create both a person, and local_user // Register the new person - let person_form = PersonInsertForm::builder() - .name(data.username.clone()) - .actor_id(Some(actor_id.clone())) - .private_key(Some(actor_keypair.private_key)) - .public_key(actor_keypair.public_key) - .inbox_url(Some(generate_inbox_url(&actor_id)?)) - .shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?)) - .instance_id(site_view.site.instance_id) - .build(); + let person_form = PersonInsertForm { + actor_id: Some(actor_id.clone()), + inbox_url: Some(generate_inbox_url(&actor_id)?), + shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?), + private_key: Some(actor_keypair.private_key), + ..PersonInsertForm::new( + data.username.clone(), + actor_keypair.public_key, + site_view.site.instance_id, + ) + }; // insert the person let inserted_person = Person::create(&mut context.pool(), &person_form) @@ -197,7 +199,8 @@ pub async fn register( verify_email_sent: false, }; - // Log the user in directly if the site is not setup, or email verification and application aren't required + // Log the user in directly if the site is not setup, or email verification and application aren't + // required if !local_site.site_setup || (!require_registration_application && !local_site.require_email_verification) { diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 4c3189a09..a9c6f657a 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -44,7 +44,7 @@ once_cell = { workspace = true } moka.workspace = true serde_with.workspace = true html2md = "0.2.14" -html2text = "0.6.0" +html2text = "0.12.5" stringreader = "0.1.1" enum_delegate = "0.2.0" diff --git a/crates/apub/src/activities/community/collection_add.rs b/crates/apub/src/activities/community/collection_add.rs index 3d5de4128..4048a1469 100644 --- a/crates/apub/src/activities/community/collection_add.rs +++ b/crates/apub/src/activities/community/collection_add.rs @@ -138,8 +138,8 @@ impl ActivityHandler for CollectionAdd { .dereference(context) .await?; - // If we had to refetch the community while parsing the activity, then the new mod has already - // been added. Skip it here as it would result in a duplicate key error. + // If we had to refetch the community while parsing the activity, then the new mod has + // already been added. Skip it here as it would result in a duplicate key error. let new_mod_id = new_mod.id; let moderated_communities = CommunityModerator::get_person_moderated_communities(&mut context.pool(), new_mod_id) diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index a47dec2bd..59b8fadbb 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -24,13 +24,14 @@ pub mod update; /// /// Activities are sent to the community itself if it lives on another instance. If the community /// is local, the activity is directly wrapped into Announce and sent to community followers. -/// Activities are also sent to those who follow the actor (with exception of moderation activities). +/// Activities are also sent to those who follow the actor (with exception of moderation +/// activities). /// /// * `activity` - The activity which is being sent /// * `actor` - The user who is sending the activity /// * `community` - Community inside which the activity is sent -/// * `inboxes` - Any additional inboxes the activity should be sent to (for example, -/// to the user who is being promoted to moderator) +/// * `inboxes` - Any additional inboxes the activity should be sent to (for example, to the user +/// who is being promoted to moderator) /// * `is_mod_activity` - True for things like Add/Mod, these are not sent to user followers pub(crate) async fn send_activity_in_community( activity: AnnouncableActivities, diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index b86e32d49..2406d2eb3 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -176,7 +176,8 @@ impl ActivityHandler for CreateOrUpdateNote { // Although mentions could be gotten from the post tags (they are included there), or the ccs, // Its much easier to scrape them from the comment body, since the API has to do that // anyway. - // TODO: for compatibility with other projects, it would be much better to read this from cc or tags + // TODO: for compatibility with other projects, it would be much better to read this from cc or + // tags let mentions = scrape_text_for_mentions(&comment.content); send_local_notifs(mentions, comment.id, &actor, do_send_email, context).await?; Ok(()) diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index 25d197007..d83b9a135 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -70,6 +70,8 @@ pub async fn list_comments( let parent_path_cloned = parent_path.clone(); let post_id = data.post_id; + let local_user = local_user_view.as_ref().map(|l| &l.local_user); + let comments = CommentQuery { listing_type, sort, @@ -80,7 +82,7 @@ pub async fn list_comments( community_id, parent_path: parent_path_cloned, post_id, - local_user: local_user_view.as_ref(), + local_user, page, limit, ..Default::default() diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index ec5412de8..31bdd0c8e 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -49,17 +49,17 @@ pub async fn list_posts( return Err(LemmyError::from(LemmyErrorType::ContradictingFilters)); } - let local_user_ref = local_user_view.as_ref().map(|u| &u.local_user); + let local_user = local_user_view.as_ref().map(|u| &u.local_user); let listing_type = Some(listing_type_with_default( data.type_, - local_user_ref, + local_user, &local_site.local_site, community_id, )); let sort = Some(sort_type_with_default( data.sort, - local_user_ref, + local_user, &local_site.local_site, )); @@ -71,7 +71,7 @@ pub async fn list_posts( }; let posts = PostQuery { - local_user: local_user_view.as_ref(), + local_user, listing_type, sort, community_id, diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index 0d65ab4f7..a6b3560aa 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -65,10 +65,12 @@ pub async fn read_person( None }; + let local_user = local_user_view.as_ref().map(|l| &l.local_user); + let posts = PostQuery { sort, saved_only, - local_user: local_user_view.as_ref(), + local_user, community_id, page, limit, @@ -79,7 +81,7 @@ pub async fn read_person( .await?; let comments = CommentQuery { - local_user: local_user_view.as_ref(), + local_user, sort: sort.map(post_to_comment_sort_type), saved_only, community_id, diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index f3cd36faf..a048b64a7 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -55,7 +55,7 @@ pub async fn search( data.community_id }; let creator_id = data.creator_id; - let local_user = local_user_view.as_ref().map(|luv| &luv.local_user); + let local_user = local_user_view.as_ref().map(|l| &l.local_user); match search_type { SearchType::Posts => { @@ -64,7 +64,7 @@ pub async fn search( listing_type: (listing_type), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_view.as_ref()), + local_user, search_term: (Some(q)), page: (page), limit: (limit), @@ -80,7 +80,7 @@ pub async fn search( search_term: (Some(q)), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_view.as_ref()), + local_user, page: (page), limit: (limit), ..Default::default() @@ -125,7 +125,7 @@ pub async fn search( listing_type: (listing_type), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_view.as_ref()), + local_user, search_term: (Some(q)), page: (page), limit: (limit), @@ -142,7 +142,7 @@ pub async fn search( search_term: (Some(q)), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_view.as_ref()), + local_user, page: (page), limit: (limit), ..Default::default() @@ -192,6 +192,7 @@ pub async fn search( community_id: (community_id), creator_id: (creator_id), url_search: (Some(q)), + local_user, page: (page), limit: (limit), ..Default::default() diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 558551632..9f2cb58c5 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -338,13 +338,11 @@ mod tests { context: &Data, ) -> LemmyResult { let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?; - let person_form = PersonInsertForm::builder() - .name(name.clone()) - .display_name(Some(name.clone())) - .bio(bio) - .public_key("asd".to_string()) - .instance_id(instance.id) - .build(); + let person_form = PersonInsertForm { + display_name: Some(name.clone()), + bio, + ..PersonInsertForm::test_form(instance.id, &name) + }; let person = Person::create(&mut context.pool(), &person_form).await?; let user_form = LocalUserInsertForm::builder() diff --git a/crates/apub/src/collections/community_featured.rs b/crates/apub/src/collections/community_featured.rs index eb19d64ea..e092693e6 100644 --- a/crates/apub/src/collections/community_featured.rs +++ b/crates/apub/src/collections/community_featured.rs @@ -72,7 +72,8 @@ impl Collection for ApubCommunityFeatured { .to_vec(); } - // process items in parallel, to avoid long delay from fetch_site_metadata() and other processing + // process items in parallel, to avoid long delay from fetch_site_metadata() and other + // processing let stickied_posts: Vec = join_all(pages.into_iter().map(|page| { async { // use separate request counter for each item, otherwise there will be problems with diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 02b912f44..8e5419c7e 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -129,11 +129,7 @@ mod tests { let inserted_instance = Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?; - let old_mod = PersonInsertForm::builder() - .name("holly".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let old_mod = PersonInsertForm::test_form(inserted_instance.id, "holly"); let old_mod = Person::create(&mut context.pool(), &old_mod).await?; let community_moderator_form = CommunityModeratorForm { diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs index 6b66c0859..8aca82d38 100644 --- a/crates/apub/src/collections/community_outbox.rs +++ b/crates/apub/src/collections/community_outbox.rs @@ -102,7 +102,8 @@ impl Collection for ApubCommunityOutbox { // We intentionally ignore errors here. This is because the outbox might contain posts from old // Lemmy versions, or from other software which we cant parse. In that case, we simply skip the // item and only parse the ones that work. - // process items in parallel, to avoid long delay from fetch_site_metadata() and other processing + // process items in parallel, to avoid long delay from fetch_site_metadata() and other + // processing join_all(outbox_activities.into_iter().map(|activity| { async { // Receiving announce requires at least one local community follower for anti spam purposes. diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 4e719895a..93c2c83ae 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -28,6 +28,7 @@ use lemmy_api_common::{ }, }; use lemmy_db_schema::{ + sensitive::SensitiveString, source::{ activity::ActorType, actor_language::CommunityLanguage, @@ -213,7 +214,7 @@ impl Actor for ApubCommunity { } fn private_key_pem(&self) -> Option { - self.private_key.clone() + self.private_key.clone().map(SensitiveString::into_inner) } fn inbox(&self) -> Url { diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 2f01609a6..24bb63b4c 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -29,6 +29,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ newtypes::InstanceId, + sensitive::SensitiveString, source::{ activity::ActorType, actor_language::SiteLanguage, @@ -187,7 +188,7 @@ impl Actor for ApubSite { } fn private_key_pem(&self) -> Option { - self.private_key.clone() + self.private_key.clone().map(SensitiveString::into_inner) } fn inbox(&self) -> Url { diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 1aac170d7..61ff04622 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -30,6 +30,7 @@ use lemmy_api_common::{ }, }; use lemmy_db_schema::{ + sensitive::SensitiveString, source::{ activity::ActorType, local_site::LocalSite, @@ -200,7 +201,7 @@ impl Actor for ApubPerson { } fn private_key_pem(&self) -> Option { - self.private_key.clone() + self.private_key.clone().map(SensitiveString::into_inner) } fn inbox(&self) -> Url { diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 0931349b6..7e4254840 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -25,12 +25,7 @@ use html2text::{from_read_with_decorator, render::text_renderer::TrivialDecorato use lemmy_api_common::{ context::LemmyContext, request::generate_post_link_metadata, - utils::{ - get_url_blocklist, - local_site_opt_to_slur_regex, - process_markdown_opt, - proxy_image_link_opt_apub, - }, + utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown_opt}, }; use lemmy_db_schema::{ source::{ @@ -224,12 +219,13 @@ impl Object for ApubPost { } else { None }; - check_url_scheme(&url)?; + + if let Some(url) = &url { + check_url_scheme(url)?; + } let alt_text = first_attachment.cloned().and_then(Attachment::alt_text); - let url = proxy_image_link_opt_apub(url, context).await?; - let slur_regex = &local_site_opt_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(context).await?; @@ -259,7 +255,8 @@ impl Object for ApubPost { let post_ = post.clone(); let context_ = context.reset_request_count(); - // Generates a post thumbnail in background task, because some sites can be very slow to respond. + // Generates a post thumbnail in background task, because some sites can be very slow to + // respond. spawn_try_task(async move { generate_post_link_metadata(post_, None, |_| None, local_site, context_).await }); diff --git a/crates/apub/src/protocol/activities/deletion/delete_user.rs b/crates/apub/src/protocol/activities/deletion/delete_user.rs index 46b070fab..70bba0718 100644 --- a/crates/apub/src/protocol/activities/deletion/delete_user.rs +++ b/crates/apub/src/protocol/activities/deletion/delete_user.rs @@ -23,6 +23,7 @@ pub struct DeleteUser { #[serde(deserialize_with = "deserialize_one_or_many", default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub(crate) cc: Vec, - /// Nonstandard field. If present, all content from the user should be deleted along with the account + /// Nonstandard field. If present, all content from the user should be deleted along with the + /// account pub(crate) remove_data: Option, } diff --git a/crates/db_perf/src/main.rs b/crates/db_perf/src/main.rs index 6139fc18e..8e03a0a1d 100644 --- a/crates/db_perf/src/main.rs +++ b/crates/db_perf/src/main.rs @@ -72,11 +72,7 @@ async fn try_main() -> LemmyResult<()> { println!("🫃 creating {} people", args.people); let mut person_ids = vec![]; for i in 0..args.people.get() { - let form = PersonInsertForm::builder() - .name(format!("p{i}")) - .public_key("pubkey".to_owned()) - .instance_id(instance.id) - .build(); + let form = PersonInsertForm::test_form(instance.id, &format!("p{i}")); person_ids.push(Person::create(&mut conn.into(), &form).await?.id); } @@ -132,7 +128,8 @@ async fn try_main() -> LemmyResult<()> { // Make sure the println above shows the correct amount assert_eq!(num_inserted_posts, num_posts as usize); - // Manually trigger and wait for a statistics update to ensure consistent and high amount of accuracy in the statistics used for query planning + // Manually trigger and wait for a statistics update to ensure consistent and high amount of + // accuracy in the statistics used for query planning println!("🧮 updating database statistics"); conn.batch_execute("ANALYZE;").await?; diff --git a/crates/db_perf/src/series.rs b/crates/db_perf/src/series.rs index 0dcddbd9f..b504efc54 100644 --- a/crates/db_perf/src/series.rs +++ b/crates/db_perf/src/series.rs @@ -14,10 +14,12 @@ use diesel::{ /// Gererates a series of rows for insertion. /// -/// An inclusive range is created from `start` and `stop`. A row for each number is generated using `selection`, which can be a tuple. -/// [`current_value`] is an expression that gets the current value. +/// An inclusive range is created from `start` and `stop`. A row for each number is generated using +/// `selection`, which can be a tuple. [`current_value`] is an expression that gets the current +/// value. /// -/// For example, if there's a `numbers` table with a `number` column, this inserts all numbers from 1 to 10 in a single statement: +/// For example, if there's a `numbers` table with a `number` column, this inserts all numbers from +/// 1 to 10 in a single statement: /// /// ``` /// dsl::insert_into(numbers::table) diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index a0654f063..dc924fa91 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -70,7 +70,7 @@ diesel_ltree = { workspace = true, optional = true } typed-builder = { workspace = true } async-trait = { workspace = true } tracing = { workspace = true } -deadpool = { version = "0.10.0", features = ["rt_tokio_1"], optional = true } +deadpool = { version = "0.12.1", features = ["rt_tokio_1"], optional = true } ts-rs = { workspace = true, optional = true } futures-util = { workspace = true } tokio = { workspace = true, optional = true } @@ -81,6 +81,7 @@ uuid = { workspace = true, features = ["v4"] } i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } moka.workspace = true +derive-new.workspace = true [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index fa5b01018..98af3e546 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -5,12 +5,17 @@ -- (even if only other columns are updated) because triggers can run after the deletion of referenced rows and -- before the automatic deletion of the row that references it. This is not a problem for insert or delete. -- --- After a row update begins, a concurrent update on the same row can't begin until the whole --- transaction that contains the first update is finished. To reduce this locking, statements in --- triggers should be ordered based on the likelihood of concurrent writers. For example, updating --- site_aggregates should be done last because the same row is updated for all local stuff. If --- it were not last, then the locking period for concurrent writers would extend to include the --- time consumed by statements that come after. +-- Triggers that update multiple tables should use this order: person_aggregates, comment_aggregates, +-- post_aggregates, community_aggregates, site_aggregates +-- * The order matters because the updated rows are locked until the end of the transaction, and statements +-- in a trigger don't use separate transactions. This means that updates closer to the beginning cause +-- longer locks because the duration of each update extends the durations of the locks caused by previous +-- updates. Long locks are worse on rows that have more concurrent transactions trying to update them. The +-- listed order starts with tables that are less likely to have such rows. +-- https://www.postgresql.org/docs/16/transaction-iso.html#XACT-READ-COMMITTED +-- * Using the same order in every trigger matters because a deadlock is possible if multiple transactions +-- update the same rows in a different order. +-- https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS -- -- -- Create triggers for both post and comments diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 915d17b1d..92b24beb5 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -64,19 +64,11 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_comment_agg".into()) - .public_key("pubkey".into()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_comment_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); - let another_person = PersonInsertForm::builder() - .name("jerry_comment_agg".into()) - .public_key("pubkey".into()) - .instance_id(inserted_instance.id) - .build(); + let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_comment_agg"); let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index 0cf63809d..fe9de62bb 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -65,19 +65,11 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_community_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); - let another_person = PersonInsertForm::builder() - .name("jerry_community_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg"); let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 03295173f..a8767895c 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -49,19 +49,11 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_user_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_user_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); - let another_person = PersonInsertForm::builder() - .name("jerry_user_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_user_agg"); let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index cb8227795..eba3a02a3 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -83,19 +83,11 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_community_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); - let another_person = PersonInsertForm::builder() - .name("jerry_community_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg"); let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); @@ -229,11 +221,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_community_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index 268a37aac..ee9a1be9c 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -42,11 +42,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("thommy_site_agg".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_site_agg"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index 682be2ed0..8483d6c20 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -531,11 +531,7 @@ mod tests { let (site, instance) = create_test_site(pool).await; - let person_form = PersonInsertForm::builder() - .name("my test person".to_string()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); + let person_form = PersonInsertForm::test_form(instance.id, "my test person"); let person = Person::create(pool, &person_form).await.unwrap(); let local_user_form = LocalUserInsertForm::builder() .person_id(person.id) @@ -647,11 +643,7 @@ mod tests { .await .unwrap(); - let person_form = PersonInsertForm::builder() - .name("my test person".to_string()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); + let person_form = PersonInsertForm::test_form(instance.id, "my test person"); let person = Person::create(pool, &person_form).await.unwrap(); let local_user_form = LocalUserInsertForm::builder() .person_id(person.id) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index eff7da26f..0ffd53f86 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -118,8 +118,9 @@ impl Crud for Comment { type IdType = CommentId; /// This is unimplemented, use [[Comment::create]] - async fn create(_pool: &mut DbPool<'_>, _comment_form: &Self::InsertForm) -> Result { - unimplemented!(); + async fn create(pool: &mut DbPool<'_>, comment_form: &Self::InsertForm) -> Result { + debug_assert!(false); + Comment::create(pool, comment_form, None).await } async fn update( @@ -233,11 +234,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("terry".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "terry"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/impls/comment_reply.rs b/crates/db_schema/src/impls/comment_reply.rs index bfea74a5a..5a33a51d7 100644 --- a/crates/db_schema/src/impls/comment_reply.rs +++ b/crates/db_schema/src/impls/comment_reply.rs @@ -87,117 +87,3 @@ impl CommentReply { .optional() } } - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] -mod tests { - - use crate::{ - source::{ - comment::{Comment, CommentInsertForm}, - comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm}, - community::{Community, CommunityInsertForm}, - instance::Instance, - person::{Person, PersonInsertForm}, - post::{Post, PostInsertForm}, - }, - traits::Crud, - utils::build_db_pool_for_tests, - }; - use pretty_assertions::assert_eq; - use serial_test::serial; - - #[tokio::test] - #[serial] - async fn test_crud() { - let pool = &build_db_pool_for_tests().await; - let pool = &mut pool.into(); - - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); - - let new_person = PersonInsertForm::builder() - .name("terrylake".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_person = Person::create(pool, &new_person).await.unwrap(); - - let recipient_form = PersonInsertForm::builder() - .name("terrylakes recipient".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap(); - - let new_community = CommunityInsertForm::builder() - .name("test community lake".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_community = Community::create(pool, &new_community).await.unwrap(); - - let new_post = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); - - let inserted_post = Post::create(pool, &new_post).await.unwrap(); - - let comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); - - let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); - - let comment_reply_form = CommentReplyInsertForm { - recipient_id: inserted_recipient.id, - comment_id: inserted_comment.id, - read: None, - }; - - let inserted_reply = CommentReply::create(pool, &comment_reply_form) - .await - .unwrap(); - - let expected_reply = CommentReply { - id: inserted_reply.id, - recipient_id: inserted_reply.recipient_id, - comment_id: inserted_reply.comment_id, - read: false, - published: inserted_reply.published, - }; - - let read_reply = CommentReply::read(pool, inserted_reply.id) - .await - .unwrap() - .unwrap(); - - let comment_reply_update_form = CommentReplyUpdateForm { read: Some(false) }; - let updated_reply = CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form) - .await - .unwrap(); - - Comment::delete(pool, inserted_comment.id).await.unwrap(); - Post::delete(pool, inserted_post.id).await.unwrap(); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Person::delete(pool, inserted_recipient.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); - - assert_eq!(expected_reply, read_reply); - assert_eq!(expected_reply, inserted_reply); - assert_eq!(expected_reply, updated_reply); - } -} diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index e886098ce..6cd90cc66 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -141,7 +141,8 @@ impl Community { Ok(community_) } - /// Get the community which has a given moderators or featured url, also return the collection type + /// Get the community which has a given moderators or featured url, also return the collection + /// type pub async fn get_by_collection_url( pool: &mut DbPool<'_>, url: &DbUrl, @@ -433,11 +434,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("bobbee".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "bobbee"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/impls/images.rs b/crates/db_schema/src/impls/images.rs index 32d9469f1..547bfc4e2 100644 --- a/crates/db_schema/src/impls/images.rs +++ b/crates/db_schema/src/impls/images.rs @@ -103,11 +103,11 @@ impl ImageDetails { pub(crate) async fn create( conn: &mut AsyncPgConnection, form: &ImageDetailsForm, - ) -> Result { + ) -> Result { insert_into(image_details::table) .values(form) .on_conflict_do_nothing() - .get_result::(conn) + .execute(conn) .await } } diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 67da14823..94bf909a3 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -94,11 +94,15 @@ impl Instance { .await } - #[cfg(test)] + /// Only for use in tests pub async fn delete_all(pool: &mut DbPool<'_>) -> Result { let conn = &mut get_conn(pool).await?; + diesel::delete(federation_queue_state::table) + .execute(conn) + .await?; diesel::delete(instance::table).execute(conn).await } + pub async fn allowlist(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; instance::table @@ -117,15 +121,15 @@ impl Instance { .await } - /// returns a list of all instances, each with a flag of whether the instance is allowed or not and dead or not - /// ordered by id + /// returns a list of all instances, each with a flag of whether the instance is allowed or not + /// and dead or not ordered by id pub async fn read_federated_with_blocked_and_dead( pool: &mut DbPool<'_>, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let is_dead_expr = coalesce(instance::updated, instance::published).lt(now() - 3.days()); - // this needs to be done in two steps because the meaning of the "blocked" column depends on the existence - // of any value at all in the allowlist. (so a normal join wouldn't work) + // this needs to be done in two steps because the meaning of the "blocked" column depends on the + // existence of any value at all in the allowlist. (so a normal join wouldn't work) let use_allowlist = federation_allowlist::table .select(count_star().gt(0)) .get_result::(conn) diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 62fc418d0..9b59e07ba 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -5,6 +5,7 @@ use crate::{ actor_language::LocalUserLanguage, local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm}, local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeInsertForm}, + site::Site, }, utils::{ functions::{coalesce, lower}, @@ -216,6 +217,44 @@ impl LocalUser { } } +/// Adds some helper functions for an optional LocalUser +pub trait LocalUserOptionHelper { + fn person_id(&self) -> Option; + fn local_user_id(&self) -> Option; + fn show_bot_accounts(&self) -> bool; + fn show_read_posts(&self) -> bool; + fn is_admin(&self) -> bool; + fn show_nsfw(&self, site: &Site) -> bool; +} + +impl LocalUserOptionHelper for Option<&LocalUser> { + fn person_id(&self) -> Option { + self.map(|l| l.person_id) + } + + fn local_user_id(&self) -> Option { + self.map(|l| l.id) + } + + fn show_bot_accounts(&self) -> bool { + self.map(|l| l.show_bot_accounts).unwrap_or(true) + } + + fn show_read_posts(&self) -> bool { + self.map(|l| l.show_read_posts).unwrap_or(true) + } + + fn is_admin(&self) -> bool { + self.map(|l| l.admin).unwrap_or(false) + } + + fn show_nsfw(&self, site: &Site) -> bool { + self + .map(|l| l.show_nsfw) + .unwrap_or(site.content_warning.is_some()) + } +} + impl LocalUserInsertForm { pub fn test_form(person_id: PersonId) -> Self { Self::builder() diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index 125bbcd51..c10d818f8 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -513,19 +513,11 @@ mod tests { .await .unwrap(); - let new_mod = PersonInsertForm::builder() - .name("the mod".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_mod = PersonInsertForm::test_form(inserted_instance.id, "the mod"); let inserted_mod = Person::create(pool, &new_mod).await.unwrap(); - let new_person = PersonInsertForm::builder() - .name("jim2".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim2"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index 762aa4656..0b1351af1 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -1,81 +1,45 @@ use crate::{ diesel::OptionalExtension, newtypes::LocalUserId, - schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token}, + schema::password_reset_request::dsl::{password_reset_request, published, token}, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, - traits::Crud, utils::{get_conn, DbPool}, }; use diesel::{ + delete, dsl::{insert_into, now, IntervalDsl}, result::Error, sql_types::Timestamptz, ExpressionMethods, IntoSql, - QueryDsl, }; use diesel_async::RunQueryDsl; -#[async_trait] -impl Crud for PasswordResetRequest { - type InsertForm = PasswordResetRequestForm; - type UpdateForm = PasswordResetRequestForm; - type IdType = i32; - - async fn create(pool: &mut DbPool<'_>, form: &PasswordResetRequestForm) -> Result { - let conn = &mut get_conn(pool).await?; - insert_into(password_reset_request) - .values(form) - .get_result::(conn) - .await - } - async fn update( - pool: &mut DbPool<'_>, - password_reset_request_id: i32, - form: &PasswordResetRequestForm, - ) -> Result { - let conn = &mut get_conn(pool).await?; - diesel::update(password_reset_request.find(password_reset_request_id)) - .set(form) - .get_result::(conn) - .await - } -} - impl PasswordResetRequest { - pub async fn create_token( + pub async fn create( pool: &mut DbPool<'_>, from_local_user_id: LocalUserId, token_: String, ) -> Result { let form = PasswordResetRequestForm { local_user_id: from_local_user_id, - token: token_, + token: token_.into(), }; - - Self::create(pool, &form).await - } - pub async fn read_from_token(pool: &mut DbPool<'_>, token_: &str) -> Result, Error> { let conn = &mut get_conn(pool).await?; - password_reset_request + insert_into(password_reset_request) + .values(form) + .get_result::(conn) + .await + } + + pub async fn read_and_delete(pool: &mut DbPool<'_>, token_: &str) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + delete(password_reset_request) .filter(token.eq(token_)) .filter(published.gt(now.into_sql::() - 1.days())) - .first(conn) - .await - .optional() - } - - pub async fn get_recent_password_resets_count( - pool: &mut DbPool<'_>, - user_id: LocalUserId, - ) -> Result { - let conn = &mut get_conn(pool).await?; - password_reset_request - .filter(local_user_id.eq(user_id)) - .filter(published.gt(now.into_sql::() - 1.days())) - .count() .get_result(conn) .await + .optional() } } @@ -94,62 +58,60 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_password_reset() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); - - let new_person = PersonInsertForm::builder() - .name("thommy prw".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_person = Person::create(pool, &new_person).await.unwrap(); - + // Setup + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy prw"); + let inserted_person = Person::create(pool, &new_person).await?; let new_local_user = LocalUserInsertForm::builder() .person_id(inserted_person.id) .password_encrypted("pass".to_string()) .build(); + let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?; - let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![]) - .await - .unwrap(); - + // Create password reset token let token = "nope"; - let inserted_password_reset_request = - PasswordResetRequest::create_token(pool, inserted_local_user.id, token.to_string()) - .await - .unwrap(); + PasswordResetRequest::create(pool, inserted_local_user.id, token.to_string()).await?; - let expected_password_reset_request = PasswordResetRequest { - id: inserted_password_reset_request.id, - local_user_id: inserted_local_user.id, - token: token.to_string(), - published: inserted_password_reset_request.published, - }; - - let read_password_reset_request = PasswordResetRequest::read_from_token(pool, token) - .await - .unwrap() + // Read it and verify + let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token) + .await? .unwrap(); - let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); - - assert_eq!(expected_password_reset_request, read_password_reset_request); assert_eq!( - expected_password_reset_request, - inserted_password_reset_request + inserted_password_reset_request.id, + read_password_reset_request.id ); + assert_eq!( + inserted_password_reset_request.local_user_id, + read_password_reset_request.local_user_id + ); + assert_eq!( + inserted_password_reset_request.token, + read_password_reset_request.token + ); + assert_eq!( + inserted_password_reset_request.published, + read_password_reset_request.published + ); + + // Cannot reuse same token again + let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token).await?; + assert!(read_password_reset_request.is_none()); + + // Cleanup + let num_deleted = Person::delete(pool, inserted_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(1, num_deleted); + Ok(()) } } diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index a4c7073da..f318a503a 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -12,7 +12,14 @@ use crate::{ traits::{ApubActor, Crud, Followable}, utils::{functions::lower, get_conn, naive_now, DbPool}, }; -use diesel::{dsl::insert_into, result::Error, CombineDsl, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{ + dsl::{insert_into, not}, + result::Error, + CombineDsl, + ExpressionMethods, + JoinOnDsl, + QueryDsl, +}; use diesel_async::RunQueryDsl; #[async_trait] @@ -55,7 +62,8 @@ impl Crud for Person { impl Person { /// Update or insert the person. /// - /// This is necessary for federation, because Activitypub doesn't distinguish between these actions. + /// This is necessary for federation, because Activitypub doesn't distinguish between these + /// actions. pub async fn upsert(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { let conn = &mut get_conn(pool).await?; insert_into(person::table) @@ -99,6 +107,8 @@ impl Person { .inner_join(post::table) .inner_join(community::table.on(post::community_id.eq(community::id))) .filter(community::local.eq(true)) + .filter(not(community::deleted)) + .filter(not(community::removed)) .filter(comment::creator_id.eq(for_creator_id)) .select(community::id) .union( @@ -115,11 +125,7 @@ impl Person { impl PersonInsertForm { pub fn test_form(instance_id: InstanceId, name: &str) -> Self { - Self::builder() - .name(name.to_owned()) - .public_key("pubkey".to_string()) - .instance_id(instance_id) - .build() + Self::new(name.to_owned(), "pubkey".to_string(), instance_id) } } @@ -239,11 +245,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("holly".into()) - .public_key("nada".to_owned()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "holly"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); @@ -262,7 +264,7 @@ mod tests { local: true, bot_account: false, private_key: None, - public_key: "nada".to_owned(), + public_key: "pubkey".to_owned(), last_refreshed_at: inserted_person.published, inbox_url: inserted_person.inbox_url.clone(), shared_inbox_url: None, @@ -302,17 +304,9 @@ mod tests { .await .unwrap(); - let person_form_1 = PersonInsertForm::builder() - .name("erich".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let person_form_1 = PersonInsertForm::test_form(inserted_instance.id, "erich"); let person_1 = Person::create(pool, &person_form_1).await.unwrap(); - let person_form_2 = PersonInsertForm::builder() - .name("michele".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let person_form_2 = PersonInsertForm::test_form(inserted_instance.id, "michele"); let person_2 = Person::create(pool, &person_form_2).await.unwrap(); let follow_form = PersonFollowerForm { diff --git a/crates/db_schema/src/impls/person_mention.rs b/crates/db_schema/src/impls/person_mention.rs index c08019170..433176683 100644 --- a/crates/db_schema/src/impls/person_mention.rs +++ b/crates/db_schema/src/impls/person_mention.rs @@ -74,117 +74,3 @@ impl PersonMention { .optional() } } - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] -mod tests { - - use crate::{ - source::{ - comment::{Comment, CommentInsertForm}, - community::{Community, CommunityInsertForm}, - instance::Instance, - person::{Person, PersonInsertForm}, - person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm}, - post::{Post, PostInsertForm}, - }, - traits::Crud, - utils::build_db_pool_for_tests, - }; - use pretty_assertions::assert_eq; - use serial_test::serial; - - #[tokio::test] - #[serial] - async fn test_crud() { - let pool = &build_db_pool_for_tests().await; - let pool = &mut pool.into(); - - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); - - let new_person = PersonInsertForm::builder() - .name("terrylake".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_person = Person::create(pool, &new_person).await.unwrap(); - - let recipient_form = PersonInsertForm::builder() - .name("terrylakes recipient".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap(); - - let new_community = CommunityInsertForm::builder() - .name("test community lake".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let inserted_community = Community::create(pool, &new_community).await.unwrap(); - - let new_post = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); - - let inserted_post = Post::create(pool, &new_post).await.unwrap(); - - let comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); - - let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); - - let person_mention_form = PersonMentionInsertForm { - recipient_id: inserted_recipient.id, - comment_id: inserted_comment.id, - read: None, - }; - - let inserted_mention = PersonMention::create(pool, &person_mention_form) - .await - .unwrap(); - - let expected_mention = PersonMention { - id: inserted_mention.id, - recipient_id: inserted_mention.recipient_id, - comment_id: inserted_mention.comment_id, - read: false, - published: inserted_mention.published, - }; - - let read_mention = PersonMention::read(pool, inserted_mention.id) - .await - .unwrap() - .unwrap(); - - let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) }; - let updated_mention = - PersonMention::update(pool, inserted_mention.id, &person_mention_update_form) - .await - .unwrap(); - Comment::delete(pool, inserted_comment.id).await.unwrap(); - Post::delete(pool, inserted_post.id).await.unwrap(); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Person::delete(pool, inserted_recipient.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); - - assert_eq!(expected_mention, read_mention); - assert_eq!(expected_mention, inserted_mention); - assert_eq!(expected_mention, updated_mention); - } -} diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index d5f1cba98..ac6cf76aa 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -401,11 +401,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("jim".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim"); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_schema/src/impls/post_report.rs b/crates/db_schema/src/impls/post_report.rs index 260574bd2..7218ef468 100644 --- a/crates/db_schema/src/impls/post_report.rs +++ b/crates/db_schema/src/impls/post_report.rs @@ -101,11 +101,7 @@ mod tests { let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) .await .unwrap(); - let person_form = PersonInsertForm::builder() - .name("jim".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let person_form = PersonInsertForm::test_form(inserted_instance.id, "jim"); let person = Person::create(pool, &person_form).await.unwrap(); let community_form = CommunityInsertForm::builder() diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index 75c7ce9bc..3cbfd052d 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -111,19 +111,11 @@ mod tests { .await .unwrap(); - let creator_form = PersonInsertForm::builder() - .name("creator_pm".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let creator_form = PersonInsertForm::test_form(inserted_instance.id, "creator_pm"); let inserted_creator = Person::create(pool, &creator_form).await.unwrap(); - let recipient_form = PersonInsertForm::builder() - .name("recipient_pm".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "recipient_pm"); let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap(); diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 37c44e263..431243221 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -24,6 +24,7 @@ pub mod aggregates; #[cfg(feature = "full")] pub mod impls; pub mod newtypes; +pub mod sensitive; #[cfg(feature = "full")] #[rustfmt::skip] #[allow(clippy::wildcard_imports)] diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index e0c516037..10abfaec4 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -127,11 +127,13 @@ pub struct LanguageId(pub i32); /// The comment reply id. pub struct CommentReplyId(i32); -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] +#[derive( + Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default, Ord, PartialOrd, +)] #[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", ts(export))] /// The instance id. -pub struct InstanceId(i32); +pub struct InstanceId(pub i32); #[derive( Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default, PartialOrd, Ord, diff --git a/crates/db_schema/src/schema_setup.rs b/crates/db_schema/src/schema_setup.rs index dc8cdf387..3def94060 100644 --- a/crates/db_schema/src/schema_setup.rs +++ b/crates/db_schema/src/schema_setup.rs @@ -6,18 +6,19 @@ use tracing::info; const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); -/// This SQL code sets up the `r` schema, which contains things that can be safely dropped and replaced -/// instead of being changed using migrations. It may not create or modify things outside of the `r` schema -/// (indicated by `r.` before the name), unless a comment says otherwise. +/// This SQL code sets up the `r` schema, which contains things that can be safely dropped and +/// replaced instead of being changed using migrations. It may not create or modify things outside +/// of the `r` schema (indicated by `r.` before the name), unless a comment says otherwise. /// -/// Currently, this code is only run after the server starts and there's at least 1 pending migration -/// to run. This means every time you change something here, you must also create a migration (a blank -/// up.sql file works fine). This behavior will be removed when we implement a better way to avoid -/// useless schema updates and locks. +/// Currently, this code is only run after the server starts and there's at least 1 pending +/// migration to run. This means every time you change something here, you must also create a +/// migration (a blank up.sql file works fine). This behavior will be removed when we implement a +/// better way to avoid useless schema updates and locks. /// -/// If you add something that depends on something (such as a table) created in a new migration, then down.sql -/// must use `CASCADE` when dropping it. This doesn't need to be fixed in old migrations because the -/// "replaceable-schema" migration runs `DROP SCHEMA IF EXISTS r CASCADE` in down.sql. +/// If you add something that depends on something (such as a table) created in a new migration, +/// then down.sql must use `CASCADE` when dropping it. This doesn't need to be fixed in old +/// migrations because the "replaceable-schema" migration runs `DROP SCHEMA IF EXISTS r CASCADE` in +/// down.sql. const REPLACEABLE_SCHEMA: &[&str] = &[ "DROP SCHEMA IF EXISTS r CASCADE;", "CREATE SCHEMA r;", @@ -29,9 +30,10 @@ pub fn run(db_url: &str) -> Result<(), LemmyError> { // Migrations don't support async connection let mut conn = PgConnection::establish(db_url).with_context(|| "Error connecting to database")?; - // Run all pending migrations except for the newest one, then run the newest one in the same transaction - // as `REPLACEABLE_SCHEMA`. This code will be becone less hacky when the conditional setup of things in - // `REPLACEABLE_SCHEMA` is done without using the number of pending migrations. + // Run all pending migrations except for the newest one, then run the newest one in the same + // transaction as `REPLACEABLE_SCHEMA`. This code will be becone less hacky when the conditional + // setup of things in `REPLACEABLE_SCHEMA` is done without using the number of pending + // migrations. info!("Running Database migrations (This may take a long time)..."); let migrations = conn .pending_migrations(MIGRATIONS) diff --git a/crates/db_schema/src/sensitive.rs b/crates/db_schema/src/sensitive.rs new file mode 100644 index 000000000..340679e2f --- /dev/null +++ b/crates/db_schema/src/sensitive.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, ops::Deref}; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Default)] +#[cfg_attr(feature = "full", derive(DieselNewType))] +#[serde(transparent)] +pub struct SensitiveString(String); + +impl SensitiveString { + pub fn into_inner(self) -> String { + self.0 + } +} + +impl Debug for SensitiveString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Sensitive").finish() + } +} + +impl AsRef<[u8]> for SensitiveString { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Deref for SensitiveString { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for SensitiveString { + fn from(t: String) -> Self { + SensitiveString(t) + } +} + +#[cfg(feature = "full")] +impl TS for SensitiveString { + fn name() -> String { + "string".to_string() + } + fn name_with_type_args(_args: Vec) -> String { + "string".to_string() + } + fn dependencies() -> Vec { + Vec::new() + } + fn transparent() -> bool { + true + } +} diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 3ebea42c1..74ae0b7f6 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -41,7 +41,8 @@ pub struct Comment { #[cfg(feature = "full")] #[cfg_attr(feature = "full", serde(with = "LtreeDef"))] #[cfg_attr(feature = "full", ts(type = "string"))] - /// The path / tree location of a comment, separated by dots, ending with the comment's id. Ex: 0.24.27 + /// The path / tree location of a comment, separated by dots, ending with the comment's id. Ex: + /// 0.24.27 pub path: Ltree, #[cfg(not(feature = "full"))] pub path: String, diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 16a17819c..fe7f120ec 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -2,6 +2,7 @@ use crate::schema::{community, community_follower, community_moderator, community_person_ban}; use crate::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, + sensitive::SensitiveString, source::placeholder_apub_url, CommunityVisibility, }; @@ -39,7 +40,7 @@ pub struct Community { /// Whether the community is local. pub local: bool, #[serde(skip)] - pub private_key: Option, + pub private_key: Option, #[serde(skip)] pub public_key: String, #[serde(skip)] diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index f159232f7..5d592ddf1 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -2,6 +2,7 @@ use crate::schema::local_user; use crate::{ newtypes::{LocalUserId, PersonId}, + sensitive::SensitiveString, ListingType, PostListingMode, SortType, @@ -24,8 +25,8 @@ pub struct LocalUser { /// The person_id for the local user. pub person_id: PersonId, #[serde(skip)] - pub password_encrypted: String, - pub email: Option, + pub password_encrypted: SensitiveString, + pub email: Option, /// Whether to show NSFW content. pub show_nsfw: bool, pub theme: String, @@ -47,7 +48,7 @@ pub struct LocalUser { /// Whether their registration application has been accepted. pub accepted_application: bool, #[serde(skip)] - pub totp_2fa_secret: Option, + pub totp_2fa_secret: Option, /// Open links in a new tab. pub open_links_in_new_tab: bool, pub blur_nsfw: bool, @@ -61,7 +62,8 @@ pub struct LocalUser { pub totp_2fa_enabled: bool, /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). pub enable_keyboard_navigation: bool, - /// Whether user avatars and inline images in the UI that are gifs should be allowed to play or should be paused + /// Whether user avatars and inline images in the UI that are gifs should be allowed to play or + /// should be paused pub enable_animated_images: bool, /// Whether to auto-collapse bot comments. pub collapse_bot_comments: bool, diff --git a/crates/db_schema/src/source/login_token.rs b/crates/db_schema/src/source/login_token.rs index e5d61f7c3..38aac33ef 100644 --- a/crates/db_schema/src/source/login_token.rs +++ b/crates/db_schema/src/source/login_token.rs @@ -1,6 +1,6 @@ -use crate::newtypes::LocalUserId; #[cfg(feature = "full")] use crate::schema::login_token; +use crate::{newtypes::LocalUserId, sensitive::SensitiveString}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -18,7 +18,7 @@ use ts_rs::TS; pub struct LoginToken { /// Jwt token for this login #[serde(skip)] - pub token: String, + pub token: SensitiveString, pub user_id: LocalUserId, /// Time of login pub published: DateTime, @@ -31,7 +31,7 @@ pub struct LoginToken { #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = login_token))] pub struct LoginTokenCreateForm { - pub token: String, + pub token: SensitiveString, pub user_id: LocalUserId, pub ip: Option, pub user_agent: Option, diff --git a/crates/db_schema/src/source/mod.rs b/crates/db_schema/src/source/mod.rs index 3a6501717..bbc8aafa2 100644 --- a/crates/db_schema/src/source/mod.rs +++ b/crates/db_schema/src/source/mod.rs @@ -43,7 +43,8 @@ pub mod tagline; /// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip). /// /// This is necessary so they can be successfully deserialized from API responses, even though the -/// value is not sent by Lemmy. Necessary for crates which rely on Rust API such as lemmy-stats-crawler. +/// value is not sent by Lemmy. Necessary for crates which rely on Rust API such as +/// lemmy-stats-crawler. fn placeholder_apub_url() -> DbUrl { DbUrl(Box::new( Url::parse("http://example.com").expect("parse placeholder url"), diff --git a/crates/db_schema/src/source/password_reset_request.rs b/crates/db_schema/src/source/password_reset_request.rs index edc030e9c..dbc930b8e 100644 --- a/crates/db_schema/src/source/password_reset_request.rs +++ b/crates/db_schema/src/source/password_reset_request.rs @@ -1,6 +1,6 @@ -use crate::newtypes::LocalUserId; #[cfg(feature = "full")] use crate::schema::password_reset_request; +use crate::{newtypes::LocalUserId, sensitive::SensitiveString}; use chrono::{DateTime, Utc}; #[derive(PartialEq, Eq, Debug)] @@ -9,7 +9,7 @@ use chrono::{DateTime, Utc}; #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PasswordResetRequest { pub id: i32, - pub token: String, + pub token: SensitiveString, pub published: DateTime, pub local_user_id: LocalUserId, } @@ -18,5 +18,5 @@ pub struct PasswordResetRequest { #[cfg_attr(feature = "full", diesel(table_name = password_reset_request))] pub struct PasswordResetRequestForm { pub local_user_id: LocalUserId, - pub token: String, + pub token: SensitiveString, } diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 25e65ae79..332b46eb5 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -2,6 +2,7 @@ use crate::schema::{person, person_follower}; use crate::{ newtypes::{DbUrl, InstanceId, PersonId}, + sensitive::SensitiveString, source::placeholder_apub_url, }; use chrono::{DateTime, Utc}; @@ -9,7 +10,6 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] use ts_rs::TS; -use typed_builder::TypedBuilder; #[skip_serializing_none] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -36,7 +36,7 @@ pub struct Person { /// Whether the person is local to our site. pub local: bool, #[serde(skip)] - pub private_key: Option, + pub private_key: Option, #[serde(skip)] pub public_key: String, #[serde(skip)] @@ -59,33 +59,46 @@ pub struct Person { pub instance_id: InstanceId, } -#[derive(Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = person))] pub struct PersonInsertForm { - #[builder(!default)] pub name: String, - #[builder(!default)] pub public_key: String, - #[builder(!default)] pub instance_id: InstanceId, + #[new(default)] pub display_name: Option, + #[new(default)] pub avatar: Option, + #[new(default)] pub banned: Option, + #[new(default)] pub published: Option>, + #[new(default)] pub updated: Option>, + #[new(default)] pub actor_id: Option, + #[new(default)] pub bio: Option, + #[new(default)] pub local: Option, + #[new(default)] pub private_key: Option, + #[new(default)] pub last_refreshed_at: Option>, + #[new(default)] pub banner: Option, + #[new(default)] pub deleted: Option, + #[new(default)] pub inbox_url: Option, + #[new(default)] pub shared_inbox_url: Option, + #[new(default)] pub matrix_user_id: Option, + #[new(default)] pub bot_account: Option, + #[new(default)] pub ban_expires: Option>, } diff --git a/crates/db_schema/src/source/secret.rs b/crates/db_schema/src/source/secret.rs index 164670a24..36c0b691b 100644 --- a/crates/db_schema/src/source/secret.rs +++ b/crates/db_schema/src/source/secret.rs @@ -1,5 +1,6 @@ #[cfg(feature = "full")] use crate::schema::secret; +use crate::sensitive::SensitiveString; #[derive(Clone)] #[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable))] @@ -7,5 +8,5 @@ use crate::schema::secret; #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct Secret { pub id: i32, - pub jwt_secret: String, + pub jwt_secret: SensitiveString, } diff --git a/crates/db_schema/src/source/site.rs b/crates/db_schema/src/source/site.rs index 83d23c779..325bff97c 100644 --- a/crates/db_schema/src/source/site.rs +++ b/crates/db_schema/src/source/site.rs @@ -1,6 +1,9 @@ -use crate::newtypes::{DbUrl, InstanceId, SiteId}; #[cfg(feature = "full")] use crate::schema::site; +use crate::{ + newtypes::{DbUrl, InstanceId, SiteId}, + sensitive::SensitiveString, +}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -35,7 +38,7 @@ pub struct Site { /// The site inbox pub inbox_url: DbUrl, #[serde(skip)] - pub private_key: Option, + pub private_key: Option, // TODO: mark as `serde(skip)` in next major release as its not needed for api pub public_key: String, pub instance_id: InstanceId, @@ -73,7 +76,8 @@ pub struct SiteUpdateForm { pub name: Option, pub sidebar: Option>, pub updated: Option>>, - // when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column. + // when you want to null out a column, you have to send Some(None)), since sending None means you + // just don't want to update that column. pub icon: Option>, pub banner: Option>, pub description: Option>, diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 139ec0e15..2b0da6c7f 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -25,8 +25,8 @@ pub type Find = dsl::Find<::Table, ::IdType>; pub type PrimaryKey = <::Table as Table>::PrimaryKey; -// Trying to create default implementations for `create` and `update` results in a lifetime mess and weird compile errors. -// https://github.com/rust-lang/rust/issues/102211 +// Trying to create default implementations for `create` and `update` results in a lifetime mess and +// weird compile errors. https://github.com/rust-lang/rust/issues/102211 #[async_trait] pub trait Crud: HasTable + Sized where @@ -49,7 +49,8 @@ where query.first(conn).await.optional() } - /// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column. + /// when you want to null out a column, you have to send Some(None)), since sending None means you + /// just don't want to update that column. async fn update( pool: &mut DbPool<'_>, id: Self::IdType, diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index e75d7ac51..fdfe7504c 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,7 +1,15 @@ -use crate::{newtypes::DbUrl, CommentSortType, SortType}; +use crate::{ + diesel::ExpressionMethods, + newtypes::{DbUrl, PersonId}, + schema::community, + CommentSortType, + CommunityVisibility, + SortType, +}; use chrono::{DateTime, TimeDelta, Utc}; use deadpool::Runtime; use diesel::{ + dsl, helper_types::AsExprOf, pg::Pg, query_builder::{Query, QueryFragment}, @@ -29,6 +37,7 @@ use i_love_jesus::CursorKey; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, settings::SETTINGS, + utils::validation::clean_url_params, }; use once_cell::sync::Lazy; use regex::Regex; @@ -61,7 +70,8 @@ pub const RANK_DEFAULT: f64 = 0.0001; pub type ActualDbPool = Pool; -/// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit reborrowing. +/// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit +/// reborrowing. /// /// https://github.com/rust-lang/rfcs/issues/1403 pub enum DbPool<'a> { @@ -101,7 +111,8 @@ impl<'a> DerefMut for DbConn<'a> { } } -// Allows functions that take `DbPool<'_>` to be called in a transaction by passing `&mut conn.into()` +// Allows functions that take `DbPool<'_>` to be called in a transaction by passing `&mut +// conn.into()` impl<'a> From<&'a mut AsyncPgConnection> for DbPool<'a> { fn from(value: &'a mut AsyncPgConnection) -> Self { DbPool::Conn(value) @@ -120,11 +131,13 @@ impl<'a> From<&'a ActualDbPool> for DbPool<'a> { } } -/// Runs multiple async functions that take `&mut DbPool<'_>` as input and return `Result`. Only works when the `futures` crate is listed in `Cargo.toml`. +/// Runs multiple async functions that take `&mut DbPool<'_>` as input and return `Result`. Only +/// works when the `futures` crate is listed in `Cargo.toml`. /// /// `$pool` is the value given to each function. /// -/// A `Result` is returned (not in a `Future`, so don't use `.await`). The `Ok` variant contains a tuple with the values returned by the given functions. +/// A `Result` is returned (not in a `Future`, so don't use `.await`). The `Ok` variant contains a +/// tuple with the values returned by the given functions. /// /// The functions run concurrently if `$pool` has the `DbPool::Pool` variant. #[macro_export] @@ -283,37 +296,35 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } -pub fn diesel_option_overwrite(opt: Option) -> Option> { +/// Takes an API text input, and converts it to an optional diesel DB update. +pub fn diesel_string_update(opt: Option<&str>) -> Option> { match opt { // An empty string is an erase - Some(unwrapped) => { - if !unwrapped.eq("") { - Some(Some(unwrapped)) - } else { - Some(None) - } - } + Some("") => Some(None), + Some(str) => Some(Some(str.into())), None => None, } } -pub fn diesel_option_overwrite_to_url(opt: &Option) -> LemmyResult>> { - match opt.as_ref().map(String::as_str) { +/// Takes an optional API URL-type input, and converts it to an optional diesel DB update. +/// Also cleans the url params. +pub fn diesel_url_update(opt: Option<&str>) -> LemmyResult>> { + match opt { // An empty string is an erase Some("") => Ok(Some(None)), Some(str_url) => Url::parse(str_url) - .map(|u| Some(Some(u.into()))) + .map(|u| Some(Some(clean_url_params(&u).into()))) .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } } -pub fn diesel_option_overwrite_to_url_create(opt: &Option) -> LemmyResult> { - match opt.as_ref().map(String::as_str) { - // An empty string is nothing - Some("") => Ok(None), +/// Takes an optional API URL-type input, and converts it to an optional diesel DB create. +/// Also cleans the url params. +pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult> { + match opt { Some(str_url) => Url::parse(str_url) - .map(|u| Some(u.into())) + .map(|u| Some(clean_url_params(&u).into())) .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } @@ -337,8 +348,10 @@ fn establish_connection(config: &str) -> BoxFuture LemmyResult { let pool = Pool::builder(manager) .max_size(SETTINGS.database.pool_size) .runtime(Runtime::Tokio1) - // Limit connection age to prevent use of prepared statements that have query plans based on very old statistics + // Limit connection age to prevent use of prepared statements that have query plans based on + // very old statistics .pre_recycle(Hook::sync_fn(|_conn, metrics| { - // Preventing the first recycle can cause an infinite loop when trying to get a new connection from the pool + // Preventing the first recycle can cause an infinite loop when trying to get a new connection + // from the pool let conn_was_used = metrics.recycled.is_some(); if metrics.age() > Duration::from_secs(3 * 24 * 60 * 60) && conn_was_used { Err(HookError::Continue(None)) @@ -508,7 +523,8 @@ pub trait ListFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec> impl<'a, T, Args, F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec>> ListFn<'a, T, Args> for F {} -/// Allows read and list functions to capture a shared closure that has an inferred return type, which is useful for join logic +/// Allows read and list functions to capture a shared closure that has an inferred return type, +/// which is useful for join logic pub struct Queries { pub read_fn: RF, pub list_fn: LF, @@ -559,8 +575,21 @@ impl Queries { } } +pub fn visible_communities_only(my_person_id: Option, query: Q) -> Q +where + Q: diesel::query_dsl::methods::FilterDsl< + dsl::Eq, + Output = Q, + >, +{ + if my_person_id.is_none() { + query.filter(community::visibility.eq(CommunityVisibility::Public)) + } else { + query + } +} + #[cfg(test)] -#[allow(clippy::unwrap_used)] #[allow(clippy::indexing_slicing)] mod tests { @@ -584,26 +613,24 @@ mod tests { #[test] fn test_diesel_option_overwrite() { - assert_eq!(diesel_option_overwrite(None), None); - assert_eq!(diesel_option_overwrite(Some(String::new())), Some(None)); + assert_eq!(diesel_string_update(None), None); + assert_eq!(diesel_string_update(Some("")), Some(None)); assert_eq!( - diesel_option_overwrite(Some("test".to_string())), + diesel_string_update(Some("test")), Some(Some("test".to_string())) ); } #[test] - fn test_diesel_option_overwrite_to_url() { - assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None))); - assert!(matches!( - diesel_option_overwrite_to_url(&Some(String::new())), - Ok(Some(None)) - )); - assert!(diesel_option_overwrite_to_url(&Some("invalid_url".to_string())).is_err()); + fn test_diesel_option_overwrite_to_url() -> LemmyResult<()> { + assert!(matches!(diesel_url_update(None), Ok(None))); + assert!(matches!(diesel_url_update(Some("")), Ok(Some(None)))); + assert!(diesel_url_update(Some("invalid_url")).is_err()); let example_url = "https://example.com"; assert!(matches!( - diesel_option_overwrite_to_url(&Some(example_url.to_string())), - Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into() + diesel_url_update(Some(example_url)), + Ok(Some(Some(url))) if url == Url::parse(example_url)?.into() )); + Ok(()) } } diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index e524e9a30..950d061ba 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -150,7 +150,8 @@ fn queries<'a>() -> Queries< query = query.filter(comment_report::comment_id.eq(comment_id)); } - // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO) + // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest + // first (FIFO) if options.unresolved_only { query = query .filter(comment_report::resolved.eq(false)) @@ -296,11 +297,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("timmy_crv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_crv"); let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); @@ -318,20 +315,12 @@ mod tests { counts: Default::default(), }; - let new_person_2 = PersonInsertForm::builder() - .name("sara_crv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_crv"); let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); // Add a third person, since new ppl can only report something once. - let new_person_3 = PersonInsertForm::builder() - .name("jessica_crv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_crv"); let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap(); diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index f6c443dcb..61dbceb4b 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -1,5 +1,4 @@ -use crate::structs::{CommentView, LocalUserView}; -use chrono::{DateTime, Utc}; +use crate::structs::CommentView; use diesel::{ dsl::{exists, not}, pg::Pg, @@ -17,6 +16,7 @@ use diesel::{ use diesel_async::RunQueryDsl; use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions}; use lemmy_db_schema::{ + impls::local_user::LocalUserOptionHelper, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, schema::{ comment, @@ -35,9 +35,18 @@ use lemmy_db_schema::{ person_block, post, }, - utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + source::local_user::LocalUser, + utils::{ + fuzzy_search, + limit_and_offset, + visible_communities_only, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, CommentSortType, - CommunityVisibility, ListingType, }; @@ -63,17 +72,6 @@ fn queries<'a>() -> Queries< ) }; - let is_saved = |person_id| { - comment_saved::table - .filter( - comment::id - .eq(comment_saved::comment_id) - .and(comment_saved::person_id.eq(person_id)), - ) - .select(comment_saved::published.nullable()) - .single_value() - }; - let is_community_followed = |person_id| { community_follower::table .filter( @@ -147,14 +145,6 @@ fn queries<'a>() -> Queries< Box::new(None::.into_sql::>()) }; - let is_saved_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(None::>.into_sql::>()) - }; - let is_creator_blocked_selection: Box> = if let Some(person_id) = my_person_id { Box::new(is_creator_blocked(person_id)) @@ -167,6 +157,13 @@ fn queries<'a>() -> Queries< .inner_join(post::table) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(comment_aggregates::table) + .left_join( + comment_saved::table.on( + comment::id + .eq(comment_saved::comment_id) + .and(comment_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))), + ), + ) .select(( comment::all_columns, person::all_columns, @@ -178,7 +175,7 @@ fn queries<'a>() -> Queries< creator_is_moderator, creator_is_admin, subscribed_type_selection, - is_saved_selection.is_not_null(), + comment_saved::person_id.nullable().is_not_null(), is_creator_blocked_selection, score_selection, )) @@ -187,22 +184,19 @@ fn queries<'a>() -> Queries< let read = move |mut conn: DbConn<'a>, (comment_id, my_person_id): (CommentId, Option)| async move { let mut query = all_joins(comment::table.find(comment_id).into_boxed(), my_person_id); - // Hide local only communities from unauthenticated users - if my_person_id.is_none() { - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); - } + query = visible_communities_only(my_person_id, query); query.first(&mut conn).await }; let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move { - let my_person_id = options.local_user.map(|l| l.person.id); - let my_local_user_id = options.local_user.map(|l| l.local_user.id); - // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); + let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); + let local_user_id_join = options + .local_user + .local_user_id() + .unwrap_or(LocalUserId(-1)); - let mut query = all_joins(comment::table.into_boxed(), my_person_id); + let mut query = all_joins(comment::table.into_boxed(), options.local_user.person_id()); if let Some(creator_id) = options.creator_id { query = query.filter(comment::creator_id.eq(creator_id)); @@ -238,7 +232,7 @@ fn queries<'a>() -> Queries< ); match listing_type { - ListingType::Subscribed => query = query.filter(is_subscribed), // TODO could be this: and(community_follower::person_id.eq(person_id_join)), + ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ ListingType::Local => { query = query .filter(community::local.eq(true)) @@ -260,11 +254,11 @@ fn queries<'a>() -> Queries< // If its saved only, then filter, and order by the saved time, not the comment creation time. if options.saved_only { query = query - .filter(is_saved(person_id_join).is_not_null()) - .then_order_by(is_saved(person_id_join).desc()); + .filter(comment_saved::person_id.is_not_null()) + .then_order_by(comment_saved::published.desc()); } - if let Some(my_id) = my_person_id { + if let Some(my_id) = options.local_user.person_id() { let not_creator_filter = comment::creator_id.ne(my_id); if options.liked_only { query = query.filter(not_creator_filter).filter(score(my_id).eq(1)); @@ -273,11 +267,7 @@ fn queries<'a>() -> Queries< } } - if !options - .local_user - .map(|l| l.local_user.show_bot_accounts) - .unwrap_or(true) - { + if !options.local_user.show_bot_accounts() { query = query.filter(person::bot_account.eq(false)); }; @@ -311,10 +301,7 @@ fn queries<'a>() -> Queries< query = query.filter(not(is_creator_blocked(person_id_join))); }; - // Hide comments in local only communities from unauthenticated users - if options.local_user.is_none() { - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); - } + query = visible_communities_only(options.local_user.person_id(), query); // A Max depth given means its a tree fetch let (limit, offset) = if let Some(max_depth) = options.max_depth { @@ -327,7 +314,8 @@ fn queries<'a>() -> Queries< query = query.filter(nlevel(comment::path).le(depth_limit)); - // only order if filtering by a post id, or parent_path. DOS potential otherwise and max_depth + !post_id isn't used anyways (afaik) + // only order if filtering by a post id, or parent_path. DOS potential otherwise and max_depth + // + !post_id isn't used anyways (afaik) if options.post_id.is_some() || options.parent_path.is_some() { // Always order by the parent path first query = query.then_order_by(subpath(comment::path, 0, -1)); @@ -408,7 +396,7 @@ pub struct CommentQuery<'a> { pub post_id: Option, pub parent_path: Option, pub creator_id: Option, - pub local_user: Option<&'a LocalUserView>, + pub local_user: Option<&'a LocalUser>, pub search_term: Option, pub saved_only: bool, pub liked_only: bool, @@ -437,7 +425,6 @@ impl<'a> CommentQuery<'a> { } #[cfg(test)] -#[allow(clippy::unwrap_used)] #[allow(clippy::indexing_slicing)] mod tests { @@ -498,32 +485,20 @@ mod tests { inserted_community: Community, } - async fn init_data(pool: &mut DbPool<'_>) -> Data { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; - let timmy_person_form = PersonInsertForm::builder() - .name("timmy".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - let inserted_timmy_person = Person::create(pool, &timmy_person_form).await.unwrap(); + let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy"); + let inserted_timmy_person = Person::create(pool, &timmy_person_form).await?; let timmy_local_user_form = LocalUserInsertForm::builder() .person_id(inserted_timmy_person.id) .admin(Some(true)) .password_encrypted(String::new()) .build(); - let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]) - .await - .unwrap(); + let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?; - let sara_person_form = PersonInsertForm::builder() - .name("sara".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - let inserted_sara_person = Person::create(pool, &sara_person_form).await.unwrap(); + let sara_person_form = PersonInsertForm::test_form(inserted_instance.id, "sara"); + let inserted_sara_person = Person::create(pool, &sara_person_form).await?; let new_community = CommunityInsertForm::builder() .name("test community 5".to_string()) @@ -532,7 +507,7 @@ mod tests { .instance_id(inserted_instance.id) .build(); - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let inserted_community = Community::create(pool, &new_community).await?; let new_post = PostInsertForm::builder() .name("A test post 2".into()) @@ -540,8 +515,8 @@ mod tests { .community_id(inserted_community.id) .build(); - let inserted_post = Post::create(pool, &new_post).await.unwrap(); - let english_id = Language::read_id_from_code(pool, Some("en")).await.unwrap(); + let inserted_post = Post::create(pool, &new_post).await?; + let english_id = Language::read_id_from_code(pool, Some("en")).await?; // Create a comment tree with this hierarchy // 0 @@ -558,7 +533,7 @@ mod tests { .language_id(english_id) .build(); - let inserted_comment_0 = Comment::create(pool, &comment_form_0, None).await.unwrap(); + let inserted_comment_0 = Comment::create(pool, &comment_form_0, None).await?; let comment_form_1 = CommentInsertForm::builder() .content("Comment 1, A test blocked comment".into()) @@ -567,11 +542,10 @@ mod tests { .language_id(english_id) .build(); - let inserted_comment_1 = Comment::create(pool, &comment_form_1, Some(&inserted_comment_0.path)) - .await - .unwrap(); + let inserted_comment_1 = + Comment::create(pool, &comment_form_1, Some(&inserted_comment_0.path)).await?; - let finnish_id = Language::read_id_from_code(pool, Some("fi")).await.unwrap(); + let finnish_id = Language::read_id_from_code(pool, Some("fi")).await?; let comment_form_2 = CommentInsertForm::builder() .content("Comment 2".into()) .creator_id(inserted_timmy_person.id) @@ -579,9 +553,8 @@ mod tests { .language_id(finnish_id) .build(); - let inserted_comment_2 = Comment::create(pool, &comment_form_2, Some(&inserted_comment_0.path)) - .await - .unwrap(); + let inserted_comment_2 = + Comment::create(pool, &comment_form_2, Some(&inserted_comment_0.path)).await?; let comment_form_3 = CommentInsertForm::builder() .content("Comment 3".into()) @@ -591,14 +564,11 @@ mod tests { .build(); let _inserted_comment_3 = - Comment::create(pool, &comment_form_3, Some(&inserted_comment_1.path)) - .await - .unwrap(); + Comment::create(pool, &comment_form_3, Some(&inserted_comment_1.path)).await?; let polish_id = Language::read_id_from_code(pool, Some("pl")) - .await - .unwrap() - .unwrap(); + .await? + .ok_or(LemmyErrorType::LanguageNotAllowed)?; let comment_form_4 = CommentInsertForm::builder() .content("Comment 4".into()) .creator_id(inserted_timmy_person.id) @@ -606,9 +576,8 @@ mod tests { .language_id(Some(polish_id)) .build(); - let inserted_comment_4 = Comment::create(pool, &comment_form_4, Some(&inserted_comment_1.path)) - .await - .unwrap(); + let inserted_comment_4 = + Comment::create(pool, &comment_form_4, Some(&inserted_comment_1.path)).await?; let comment_form_5 = CommentInsertForm::builder() .content("Comment 5".into()) @@ -617,18 +586,14 @@ mod tests { .build(); let _inserted_comment_5 = - Comment::create(pool, &comment_form_5, Some(&inserted_comment_4.path)) - .await - .unwrap(); + Comment::create(pool, &comment_form_5, Some(&inserted_comment_4.path)).await?; let timmy_blocks_sara_form = PersonBlockForm { person_id: inserted_timmy_person.id, target_id: inserted_sara_person.id, }; - let inserted_block = PersonBlock::block(pool, &timmy_blocks_sara_form) - .await - .unwrap(); + let inserted_block = PersonBlock::block(pool, &timmy_blocks_sara_form).await?; let expected_block = PersonBlock { person_id: inserted_timmy_person.id, @@ -644,7 +609,7 @@ mod tests { score: 1, }; - let _inserted_comment_like = CommentLike::like(pool, &comment_like_form).await.unwrap(); + let _inserted_comment_like = CommentLike::like(pool, &comment_like_form).await?; let timmy_local_user_view = LocalUserView { local_user: inserted_timmy_local_user.clone(), @@ -652,7 +617,7 @@ mod tests { person: inserted_timmy_person.clone(), counts: Default::default(), }; - Data { + Ok(Data { inserted_instance, inserted_comment_0, inserted_comment_1, @@ -661,7 +626,7 @@ mod tests { timmy_local_user_view, inserted_sara_person, inserted_community, - } + }) } #[tokio::test] @@ -669,7 +634,7 @@ mod tests { async fn test_crud() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; let expected_comment_view_no_person = expected_comment_view(&data, pool).await?; @@ -694,7 +659,7 @@ mod tests { let read_comment_views_with_person = CommentQuery { sort: (Some(CommentSortType::Old)), post_id: (Some(data.inserted_post.id)), - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } .list(pool) @@ -714,7 +679,7 @@ mod tests { Some(data.timmy_local_user_view.person.id), ) .await? - .unwrap(); + .ok_or(LemmyErrorType::CouldntFindComment)?; // Make sure block set the creator blocked assert!(read_comment_from_blocked_person.creator_blocked); @@ -727,7 +692,7 @@ mod tests { async fn test_liked_only() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; // Unblock sara first let timmy_unblocks_sara_form = PersonBlockForm { @@ -743,10 +708,10 @@ mod tests { person_id: data.timmy_local_user_view.person.id, score: 1, }; - CommentLike::like(pool, &comment_like_form).await.unwrap(); + CommentLike::like(pool, &comment_like_form).await?; let read_liked_comment_views = CommentQuery { - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), liked_only: (true), ..Default::default() } @@ -762,7 +727,7 @@ mod tests { assert_length!(1, read_liked_comment_views); let read_disliked_comment_views: Vec = CommentQuery { - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), disliked_only: (true), ..Default::default() } @@ -779,7 +744,7 @@ mod tests { async fn test_comment_tree() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; let top_path = data.inserted_comment_0.path.clone(); let read_comment_views_top_path = CommentQuery { @@ -852,12 +817,12 @@ mod tests { async fn test_languages() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; // by default, user has all languages enabled and should see all comments // (except from blocked user) let all_languages = CommentQuery { - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } .list(pool) @@ -875,7 +840,7 @@ mod tests { ) .await?; let finnish_comments = CommentQuery { - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } .list(pool) @@ -901,7 +866,7 @@ mod tests { ) .await?; let undetermined_comment = CommentQuery { - local_user: (Some(&data.timmy_local_user_view)), + local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } .list(pool) @@ -916,7 +881,7 @@ mod tests { async fn test_distinguished_first() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; let form = CommentUpdateForm { distinguished: Some(true), @@ -941,7 +906,7 @@ mod tests { async fn test_creator_is_moderator() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; // Make one of the inserted persons a moderator let person_id = data.inserted_sara_person.id; @@ -972,7 +937,7 @@ mod tests { async fn test_creator_is_admin() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; let comments = CommentQuery { sort: (Some(CommentSortType::Old)), @@ -997,7 +962,7 @@ mod tests { async fn test_saved_order() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; // Save two comments let save_comment_0_form = CommentSavedForm { @@ -1014,7 +979,7 @@ mod tests { // Fetch the saved comments let comments = CommentQuery { - local_user: Some(&data.timmy_local_user_view), + local_user: Some(&data.timmy_local_user_view.local_user), saved_only: true, ..Default::default() } @@ -1173,7 +1138,7 @@ mod tests { async fn local_only_instance() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; Community::update( pool, @@ -1193,7 +1158,7 @@ mod tests { assert_eq!(0, unauthenticated_query.len()); let authenticated_query = CommentQuery { - local_user: Some(&data.timmy_local_user_view), + local_user: Some(&data.timmy_local_user_view.local_user), ..Default::default() } .list(pool) @@ -1219,7 +1184,7 @@ mod tests { async fn comment_listing_local_user_banned_from_community() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; // Test that comment view shows if local user is blocked from community let banned_from_comm_person = PersonInsertForm::test_form(data.inserted_instance.id, "jill"); @@ -1262,7 +1227,7 @@ mod tests { async fn comment_listing_local_user_not_banned_from_community() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let data = init_data(pool).await?; let comment_view = CommentView::read( pool, diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 76679df1e..e89b7d545 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -178,7 +178,8 @@ fn queries<'a>() -> Queries< query = query.filter(post::id.eq(post_id)); } - // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO) + // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest + // first (FIFO) if options.unresolved_only { query = query .filter(post_report::resolved.eq(false)) @@ -318,11 +319,7 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("timmy_prv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_prv"); let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); @@ -340,20 +337,12 @@ mod tests { counts: Default::default(), }; - let new_person_2 = PersonInsertForm::builder() - .name("sara_prv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_prv"); let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); // Add a third person, since new ppl can only report something once. - let new_person_3 = PersonInsertForm::builder() - .name("jessica_prv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_prv"); let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap(); diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 925ff079b..d472fc604 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -1,5 +1,4 @@ -use crate::structs::{LocalUserView, PaginationCursor, PostView}; -use chrono::{DateTime, Utc}; +use crate::structs::{PaginationCursor, PostView}; use diesel::{ debug_query, dsl::{exists, not, IntervalDsl}, @@ -21,6 +20,7 @@ use diesel_async::RunQueryDsl; use i_love_jesus::PaginatedQueryBuilder; use lemmy_db_schema::{ aggregates::structs::{post_aggregates_keys as key, PostAggregates}, + impls::local_user::LocalUserOptionHelper, newtypes::{CommunityId, LocalUserId, PersonId, PostId}, schema::{ community, @@ -42,13 +42,14 @@ use lemmy_db_schema::{ post_read, post_saved, }, - source::site::Site, + source::{local_user::LocalUser, site::Site}, utils::{ functions::coalesce, fuzzy_search, get_conn, limit_and_offset, now, + visible_communities_only, Commented, DbConn, DbPool, @@ -57,7 +58,6 @@ use lemmy_db_schema::{ ReadFn, ReverseTimestampKey, }, - CommunityVisibility, ListingType, SortType, }; @@ -101,17 +101,6 @@ fn queries<'a>() -> Queries< ), ); - let is_saved = |person_id| { - post_saved::table - .filter( - post_aggregates::post_id - .eq(post_saved::post_id) - .and(post_saved::person_id.eq(person_id)), - ) - .select(post_saved::published.nullable()) - .single_value() - }; - let is_read = |person_id| { exists( post_read::table.filter( @@ -163,14 +152,6 @@ fn queries<'a>() -> Queries< Box::new(false.into_sql::()) }; - let is_saved_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(None::>.into_sql::>()) - }; - let is_read_selection: Box> = if let Some(person_id) = my_person_id { Box::new(is_read(person_id)) @@ -239,6 +220,13 @@ fn queries<'a>() -> Queries< .inner_join(community::table) .inner_join(post::table) .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) + .left_join( + post_saved::table.on( + post_aggregates::post_id + .eq(post_saved::post_id) + .and(post_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))), + ), + ) .select(( post::all_columns, person::all_columns, @@ -250,7 +238,7 @@ fn queries<'a>() -> Queries< creator_is_admin, post_aggregates::all_columns, subscribed_type_selection, - is_saved_selection.is_not_null(), + post_saved::person_id.nullable().is_not_null(), is_read_selection, is_hidden_selection, is_creator_blocked_selection, @@ -301,10 +289,7 @@ fn queries<'a>() -> Queries< ); } - // Hide posts in local only communities from unauthenticated users - if my_person_id.is_none() { - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); - } + query = visible_communities_only(my_person_id, query); Commented::new(query) .text("PostView::read") @@ -313,31 +298,30 @@ fn queries<'a>() -> Queries< }; let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move { - let my_person_id = options.local_user.map(|l| l.person.id); - let my_local_user_id = options.local_user.map(|l| l.local_user.id); - // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); + let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); + let local_user_id_join = options + .local_user + .local_user_id() + .unwrap_or(LocalUserId(-1)); - let mut query = all_joins(post_aggregates::table.into_boxed(), my_person_id); + let mut query = all_joins( + post_aggregates::table.into_boxed(), + options.local_user.person_id(), + ); // hide posts from deleted communities query = query.filter(community::deleted.eq(false)); // only show deleted posts to creator - if let Some(person_id) = my_person_id { + if let Some(person_id) = options.local_user.person_id() { query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id))); } else { query = query.filter(post::deleted.eq(false)); } - let is_admin = options - .local_user - .map(|l| l.local_user.admin) - .unwrap_or(false); // only show removed posts to admin when viewing user profile - if !(options.creator_id.is_some() && is_admin) { + if !(options.creator_id.is_some() && options.local_user.is_admin()) { query = query .filter(community::removed.eq(false)) .filter(post::removed.eq(false)); @@ -351,7 +335,7 @@ fn queries<'a>() -> Queries< } if let Some(listing_type) = options.listing_type { - if let Some(person_id) = my_person_id { + if let Some(person_id) = options.local_user.person_id() { let is_subscribed = exists( community_follower::table.filter( post_aggregates::community_id @@ -408,54 +392,40 @@ fn queries<'a>() -> Queries< .filter(not(post::removed.or(post::deleted))); } - // If there is a content warning, show nsfw content by default. - let has_content_warning = site.content_warning.is_some(); - if !options - .local_user - .map(|l| l.local_user.show_nsfw) - .unwrap_or(has_content_warning) - { + if !options.local_user.show_nsfw(site) { query = query .filter(post::nsfw.eq(false)) .filter(community::nsfw.eq(false)); }; - if !options - .local_user - .map(|l| l.local_user.show_bot_accounts) - .unwrap_or(true) - { + if !options.local_user.show_bot_accounts() { query = query.filter(person::bot_account.eq(false)); }; // If its saved only, then filter, and order by the saved time, not the comment creation time. - if let (true, Some(person_id)) = (options.saved_only, my_person_id) { + if options.saved_only { query = query - .filter(is_saved(person_id).is_not_null()) - .then_order_by(is_saved(person_id).desc()); + .filter(post_saved::person_id.is_not_null()) + .then_order_by(post_saved::published.desc()); } // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read // setting wont be able to see saved posts. - else if !options - .local_user - .map(|l| l.local_user.show_read_posts) - .unwrap_or(true) - { + else if !options.local_user.show_read_posts() { // Do not hide read posts when it is a user profile view // Or, only hide read posts on non-profile views - if let (None, Some(person_id)) = (options.creator_id, my_person_id) { + if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) { query = query.filter(not(is_read(person_id))); } } if !options.show_hidden { // If a creator id isn't given (IE its on home or community pages), hide the hidden posts - if let (None, Some(person_id)) = (options.creator_id, my_person_id) { + if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) { query = query.filter(not(is_hidden(person_id))); } } - if let Some(my_id) = my_person_id { + if let Some(my_id) = options.local_user.person_id() { let not_creator_filter = post_aggregates::creator_id.ne(my_id); if options.liked_only { query = query.filter(not_creator_filter).filter(score(my_id).eq(1)); @@ -464,14 +434,11 @@ fn queries<'a>() -> Queries< } }; - // Hide posts in local only communities from unauthenticated users - if options.local_user.is_none() { - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); - } + query = visible_communities_only(options.local_user.person_id(), query); // Dont filter blocks or missing languages for moderator view type if let (Some(person_id), false) = ( - my_person_id, + options.local_user.person_id(), options.listing_type.unwrap_or_default() == ListingType::ModeratorView, ) { // Filter out the rows with missing languages @@ -620,7 +587,8 @@ impl PaginationCursor { } // currently we use a postaggregates struct as the pagination token. -// we only use some of the properties of the post aggregates, depending on which sort type we page by +// we only use some of the properties of the post aggregates, depending on which sort type we page +// by #[derive(Clone)] pub struct PaginationCursorData(PostAggregates); @@ -630,9 +598,10 @@ pub struct PostQuery<'a> { pub sort: Option, pub creator_id: Option, pub community_id: Option, - // if true, the query should be handled as if community_id was not given except adding the literal filter + // if true, the query should be handled as if community_id was not given except adding the + // literal filter pub community_id_just_for_prefetch: bool, - pub local_user: Option<&'a LocalUserView>, + pub local_user: Option<&'a LocalUser>, pub search_term: Option, pub url_search: Option, pub saved_only: bool, @@ -652,15 +621,17 @@ impl<'a> PostQuery<'a> { site: &Site, pool: &mut DbPool<'_>, ) -> Result>, Error> { - // first get one page for the most popular community to get an upper bound for the page end for the real query - // the reason this is needed is that when fetching posts for a single community PostgreSQL can optimize - // the query to use an index on e.g. (=, >=, >=, >=) and fetch only LIMIT rows - // but for the followed-communities query it has to query the index on (IN, >=, >=, >=) - // which it currently can't do at all (as of PG 16). see the discussion here: - // https://github.com/LemmyNet/lemmy/issues/2877#issuecomment-1673597190 + // first get one page for the most popular community to get an upper bound for the page end for + // the real query. the reason this is needed is that when fetching posts for a single + // community PostgreSQL can optimize the query to use an index on e.g. (=, >=, >=, >=) and + // fetch only LIMIT rows but for the followed-communities query it has to query the index on + // (IN, >=, >=, >=) which it currently can't do at all (as of PG 16). see the discussion + // here: https://github.com/LemmyNet/lemmy/issues/2877#issuecomment-1673597190 // - // the results are correct no matter which community we fetch these for, since it basically covers the "worst case" of the whole page consisting of posts from one community - // but using the largest community decreases the pagination-frame so make the real query more efficient. + // the results are correct no matter which community we fetch these for, since it basically + // covers the "worst case" of the whole page consisting of posts from one community + // but using the largest community decreases the pagination-frame so make the real query more + // efficient. use lemmy_db_schema::schema::{ community_aggregates::dsl::{community_aggregates, community_id, users_active_month}, community_follower::dsl::{ @@ -675,11 +646,7 @@ impl<'a> PostQuery<'a> { "legacy pagination cannot be combined with v2 pagination".into(), )); } - let self_person_id = self - .local_user - .expect("part of the above if") - .local_user - .person_id; + let self_person_id = self.local_user.expect("part of the above if").person_id; let largest_subscribed = { let conn = &mut get_conn(pool).await?; community_follower @@ -711,7 +678,8 @@ impl<'a> PostQuery<'a> { ) .await?; // take last element of array. if this query returned less than LIMIT elements, - // the heuristic is invalid since we can't guarantee the full query will return >= LIMIT results (return original query) + // the heuristic is invalid since we can't guarantee the full query will return >= LIMIT results + // (return original query) if (v.len() as i64) < limit { Ok(Some(self.clone())) } else { @@ -818,7 +786,7 @@ mod tests { fn default_post_query(&self) -> PostQuery<'_> { PostQuery { sort: Some(SortType::New), - local_user: Some(&self.local_user_view), + local_user: Some(&self.local_user_view.local_user), ..Default::default() } } @@ -1335,8 +1303,8 @@ mod tests { // Deleted post is only shown to creator for (local_user, expect_contains_deleted) in [ (None, false), - (Some(&data.blocked_local_user_view), false), - (Some(&data.local_user_view), true), + (Some(&data.blocked_local_user_view.local_user), false), + (Some(&data.local_user_view.local_user), true), ] { let contains_deleted = PostQuery { local_user, @@ -1432,7 +1400,8 @@ mod tests { let mut inserted_post_ids = vec![]; let mut inserted_comment_ids = vec![]; - // Create 150 posts with varying non-correlating values for publish date, number of comments, and featured + // Create 150 posts with varying non-correlating values for publish date, number of comments, + // and featured for comments in 0..10 { for _ in 0..15 { let post_form = PostInsertForm::builder() @@ -1573,7 +1542,7 @@ mod tests { // Make sure it does come back with the show_hidden option let post_listings_show_hidden = PostQuery { sort: Some(SortType::New), - local_user: Some(&data.local_user_view), + local_user: Some(&data.local_user_view.local_user), show_hidden: true, ..Default::default() } @@ -1749,7 +1718,7 @@ mod tests { assert_eq!(0, unauthenticated_query.len()); let authenticated_query = PostQuery { - local_user: Some(&data.local_user_view), + local_user: Some(&data.local_user_view.local_user), ..Default::default() } .list(&data.site, pool) diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/private_message_report_view.rs index a402d0d4f..f5e70fb3e 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -49,7 +49,8 @@ fn queries<'a>() -> Queries< let list = move |mut conn: DbConn<'a>, options: PrivateMessageReportQuery| async move { let mut query = all_joins(private_message_report::table.into_boxed()); - // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO) + // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest + // first (FIFO) if options.unresolved_only { query = query .filter(private_message_report::resolved.eq(false)) @@ -139,18 +140,10 @@ mod tests { .await .unwrap(); - let new_person_1 = PersonInsertForm::builder() - .name("timmy_mrv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_1 = PersonInsertForm::test_form(inserted_instance.id, "timmy_mrv"); let inserted_timmy = Person::create(pool, &new_person_1).await.unwrap(); - let new_person_2 = PersonInsertForm::builder() - .name("jessica_mrv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "jessica_mrv"); let inserted_jessica = Person::create(pool, &new_person_2).await.unwrap(); // timmy sends private message to jessica @@ -183,11 +176,7 @@ mod tests { assert_eq!(pm_report.reason, reports[0].private_message_report.reason); assert_eq!(pm.content, reports[0].private_message.content); - let new_person_3 = PersonInsertForm::builder() - .name("admin_mrv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "admin_mrv"); let inserted_admin = Person::create(pool, &new_person_3).await.unwrap(); // admin resolves the report (after taking appropriate action) diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index 764ef1dcb..79224d86f 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -209,27 +209,15 @@ mod tests { .await .unwrap(); - let timmy_form = PersonInsertForm::builder() - .name("timmy_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); + let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_rav"); let timmy = Person::create(pool, &timmy_form).await.unwrap(); - let sara_form = PersonInsertForm::builder() - .name("sara_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); + let sara_form = PersonInsertForm::test_form(instance.id, "sara_rav"); let sara = Person::create(pool, &sara_form).await.unwrap(); - let jess_form = PersonInsertForm::builder() - .name("jess_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); + let jess_form = PersonInsertForm::test_form(instance.id, "jess_rav"); let jess = Person::create(pool, &jess_form).await.unwrap(); diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 7346dcd0d..cd63859af 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -49,7 +49,8 @@ fn queries<'a>() -> Queries< let list = move |mut conn: DbConn<'a>, options: RegistrationApplicationQuery| async move { let mut query = all_joins(registration_application::table.into_boxed()); - // If viewing all applications, order by newest, but if viewing unresolved only, show the oldest first (FIFO) + // If viewing all applications, order by newest, but if viewing unresolved only, show the oldest + // first (FIFO) if options.unread_only { query = query .filter(registration_application::admin_id.is_null()) @@ -162,11 +163,7 @@ mod tests { .await .unwrap(); - let timmy_person_form = PersonInsertForm::builder() - .name("timmy_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_rav"); let inserted_timmy_person = Person::create(pool, &timmy_person_form).await.unwrap(); @@ -180,11 +177,7 @@ mod tests { .await .unwrap(); - let sara_person_form = PersonInsertForm::builder() - .name("sara_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let sara_person_form = PersonInsertForm::test_form(inserted_instance.id, "sara_rav"); let inserted_sara_person = Person::create(pool, &sara_person_form).await.unwrap(); @@ -212,11 +205,7 @@ mod tests { .unwrap() .unwrap(); - let jess_person_form = PersonInsertForm::builder() - .name("jess_rav".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let jess_person_form = PersonInsertForm::test_form(inserted_instance.id, "jess_rav"); let inserted_jess_person = Person::create(pool, &jess_person_form).await.unwrap(); diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index b3275dad0..3c219d63f 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -112,9 +112,10 @@ pub struct PostReportView { pub resolver: Option, } -/// currently this is just a wrapper around post id, but should be seen as opaque from the client's perspective -/// stringified since we might want to use arbitrary info later, with a P prepended to prevent ossification -/// (api users love to make assumptions (e.g. parse stuff that looks like numbers as numbers) about apis that aren't part of the spec +/// currently this is just a wrapper around post id, but should be seen as opaque from the client's +/// perspective. stringified since we might want to use arbitrary info later, with a P prepended to +/// prevent ossification (api users love to make assumptions (e.g. parse stuff that looks like +/// numbers as numbers) about apis that aren't part of the spec #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(ts_rs::TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index a0441ff4e..5daa072c3 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -112,19 +112,11 @@ mod tests { .await .unwrap(); - let new_person = PersonInsertForm::builder() - .name("timmy_vv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_vv"); let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); - let new_person_2 = PersonInsertForm::builder() - .name("sara_vv".into()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_vv"); let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index 1892055d1..d9e6a3352 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -40,6 +40,7 @@ serial_test = { workspace = true } tokio = { workspace = true } pretty_assertions = { workspace = true } url.workspace = true +lemmy_db_views.workspace = true lemmy_utils.workspace = true [package.metadata.cargo-machete] diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index baa4f5601..b1d95e719 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,6 +1,6 @@ use crate::structs::CommentReplyView; use diesel::{ - dsl::exists, + dsl::{exists, not}, pg::Pg, result::Error, sql_types, @@ -31,6 +31,7 @@ use lemmy_db_schema::{ person_block, post, }, + source::local_user::LocalUser, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, CommentSortType, }; @@ -193,6 +194,8 @@ fn queries<'a>() -> Queries< }; let list = move |mut conn: DbConn<'a>, options: CommentReplyQuery| async move { + // These filters need to be kept in sync with the filters in + // CommentReplyView::get_unread_replies() let mut query = all_joins(comment_reply::table.into_boxed(), options.my_person_id); if let Some(recipient_id) = options.recipient_id { @@ -204,7 +207,7 @@ fn queries<'a>() -> Queries< } if !options.show_bot_accounts { - query = query.filter(person::bot_account.eq(false)); + query = query.filter(not(person::bot_account)); }; query = match options.sort.unwrap_or(CommentSortType::New) { @@ -217,6 +220,11 @@ fn queries<'a>() -> Queries< CommentSortType::Top => query.order_by(comment_aggregates::score.desc()), }; + // Don't show replies from blocked persons + if let Some(my_person_id) = options.my_person_id { + query = query.filter(not(is_creator_blocked(my_person_id))); + } + let (limit, offset) = limit_and_offset(options.page, options.limit)?; query @@ -241,24 +249,33 @@ impl CommentReplyView { /// Gets the number of unread replies pub async fn get_unread_replies( pool: &mut DbPool<'_>, - my_person_id: PersonId, + local_user: &LocalUser, ) -> Result { use diesel::dsl::count; let conn = &mut get_conn(pool).await?; - comment_reply::table + let mut query = comment_reply::table .inner_join(comment::table) .left_join( person_block::table.on( comment::creator_id .eq(person_block::target_id) - .and(person_block::person_id.eq(my_person_id)), + .and(person_block::person_id.eq(local_user.person_id)), ), ) - // Dont count replies from blocked users + .inner_join(person::table.on(comment::creator_id.eq(person::id))) + .into_boxed(); + + // These filters need to be kept in sync with the filters in queries().list() + if !local_user.show_bot_accounts { + query = query.filter(not(person::bot_account)); + } + + query + // Don't count replies from blocked users .filter(person_block::person_id.is_null()) - .filter(comment_reply::recipient_id.eq(my_person_id)) + .filter(comment_reply::recipient_id.eq(local_user.person_id)) .filter(comment_reply::read.eq(false)) .filter(comment::deleted.eq(false)) .filter(comment::removed.eq(false)) @@ -268,7 +285,7 @@ impl CommentReplyView { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct CommentReplyQuery { pub my_person_id: Option, pub recipient_id: Option, @@ -284,3 +301,174 @@ impl CommentReplyQuery { queries().list(pool, self).await } } + +#[cfg(test)] +#[allow(clippy::indexing_slicing)] +mod tests { + + use crate::{comment_reply_view::CommentReplyQuery, structs::CommentReplyView}; + use lemmy_db_schema::{ + source::{ + comment::{Comment, CommentInsertForm}, + comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm}, + community::{Community, CommunityInsertForm}, + instance::Instance, + local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm}, + person::{Person, PersonInsertForm, PersonUpdateForm}, + person_block::{PersonBlock, PersonBlockForm}, + post::{Post, PostInsertForm}, + }, + traits::{Blockable, Crud}, + utils::build_db_pool_for_tests, + }; + use lemmy_db_views::structs::LocalUserView; + use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use pretty_assertions::assert_eq; + use serial_test::serial; + + #[tokio::test] + #[serial] + async fn test_crud() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + + let terry_form = PersonInsertForm::test_form(inserted_instance.id, "terrylake"); + let inserted_terry = Person::create(pool, &terry_form).await?; + + let recipient_form = PersonInsertForm { + local: Some(true), + ..PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient") + }; + + let inserted_recipient = Person::create(pool, &recipient_form).await?; + let recipient_id = inserted_recipient.id; + + let recipient_local_user = + LocalUser::create(pool, &LocalUserInsertForm::test_form(recipient_id), vec![]).await?; + + let new_community = CommunityInsertForm::builder() + .name("test community lake".to_string()) + .title("nada".to_owned()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_community = Community::create(pool, &new_community).await?; + + let new_post = PostInsertForm::builder() + .name("A test post".into()) + .creator_id(inserted_terry.id) + .community_id(inserted_community.id) + .build(); + + let inserted_post = Post::create(pool, &new_post).await?; + + let comment_form = CommentInsertForm::builder() + .content("A test comment".into()) + .creator_id(inserted_terry.id) + .post_id(inserted_post.id) + .build(); + + let inserted_comment = Comment::create(pool, &comment_form, None).await?; + + let comment_reply_form = CommentReplyInsertForm { + recipient_id: inserted_recipient.id, + comment_id: inserted_comment.id, + read: None, + }; + + let inserted_reply = CommentReply::create(pool, &comment_reply_form).await?; + + let expected_reply = CommentReply { + id: inserted_reply.id, + recipient_id: inserted_reply.recipient_id, + comment_id: inserted_reply.comment_id, + read: false, + published: inserted_reply.published, + }; + + let read_reply = CommentReply::read(pool, inserted_reply.id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; + + let comment_reply_update_form = CommentReplyUpdateForm { read: Some(false) }; + let updated_reply = + CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form).await?; + + // Test to make sure counts and blocks work correctly + let unread_replies = CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?; + + let query = CommentReplyQuery { + recipient_id: Some(recipient_id), + my_person_id: Some(recipient_id), + sort: None, + unread_only: false, + show_bot_accounts: true, + page: None, + limit: None, + }; + let replies = query.clone().list(pool).await?; + assert_eq!(1, unread_replies); + assert_eq!(1, replies.len()); + + // Block the person, and make sure these counts are now empty + let block_form = PersonBlockForm { + person_id: recipient_id, + target_id: inserted_terry.id, + }; + PersonBlock::block(pool, &block_form).await?; + + let unread_replies_after_block = + CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?; + let replies_after_block = query.clone().list(pool).await?; + assert_eq!(0, unread_replies_after_block); + assert_eq!(0, replies_after_block.len()); + + // Unblock user so we can reuse the same person + PersonBlock::unblock(pool, &block_form).await?; + + // Turn Terry into a bot account + let person_update_form = PersonUpdateForm { + bot_account: Some(true), + ..Default::default() + }; + Person::update(pool, inserted_terry.id, &person_update_form).await?; + + let recipient_local_user_update_form = LocalUserUpdateForm { + show_bot_accounts: Some(false), + ..Default::default() + }; + LocalUser::update( + pool, + recipient_local_user.id, + &recipient_local_user_update_form, + ) + .await?; + let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + + let unread_replies_after_hide_bots = + CommentReplyView::get_unread_replies(pool, &recipient_local_user_view.local_user).await?; + + let mut query_without_bots = query.clone(); + query_without_bots.show_bot_accounts = false; + let replies_after_hide_bots = query_without_bots.list(pool).await?; + assert_eq!(0, unread_replies_after_hide_bots); + assert_eq!(0, replies_after_hide_bots.len()); + + Comment::delete(pool, inserted_comment.id).await?; + Post::delete(pool, inserted_post.id).await?; + Community::delete(pool, inserted_community.id).await?; + Person::delete(pool, inserted_terry.id).await?; + Person::delete(pool, inserted_recipient.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + assert_eq!(expected_reply, read_reply); + assert_eq!(expected_reply, inserted_reply); + assert_eq!(expected_reply, updated_reply); + Ok(()) + } +} diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index b2eeda29e..7b942e043 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -14,7 +14,8 @@ use lemmy_db_schema::{ }; impl CommunityFollowerView { - /// return a list of local community ids and remote inboxes that at least one user of the given instance has followed + /// return a list of local community ids and remote inboxes that at least one user of the given + /// instance has followed pub async fn get_instance_followed_community_inboxes( pool: &mut DbPool<'_>, instance_id: InstanceId, @@ -22,8 +23,9 @@ impl CommunityFollowerView { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; // In most cases this will fetch the same url many times (the shared inbox url) - // PG will only send a single copy to rust, but it has to scan through all follower rows (same as it was before). - // So on the PG side it would be possible to optimize this further by adding e.g. a new table community_followed_instances (community_id, instance_id) + // PG will only send a single copy to rust, but it has to scan through all follower rows (same + // as it was before). So on the PG side it would be possible to optimize this further by + // adding e.g. a new table community_followed_instances (community_id, instance_id) // that would work for all instances that support fully shared inboxes. // It would be a bit more complicated though to keep it in sync. @@ -31,7 +33,8 @@ impl CommunityFollowerView { .inner_join(community::table) .inner_join(person::table) .filter(person::instance_id.eq(instance_id)) - .filter(community::local) // this should be a no-op since community_followers table only has local-person+remote-community or remote-person+local-community + .filter(community::local) // this should be a no-op since community_followers table only has + // local-person+remote-community or remote-person+local-community .filter(not(person::local)) .filter(community_follower::published.gt(published_since.naive_utc())) .select(( diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index b5c23c6ef..25e76c7b3 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -11,6 +11,7 @@ use diesel::{ }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ + impls::local_user::LocalUserOptionHelper, newtypes::{CommunityId, PersonId}, schema::{ community, @@ -19,11 +20,18 @@ use lemmy_db_schema::{ community_follower, community_person_ban, instance_block, - local_user, }, source::{community::CommunityFollower, local_user::LocalUser, site::Site}, - utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, - CommunityVisibility, + utils::{ + fuzzy_search, + limit_and_offset, + visible_communities_only, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, ListingType, SortType, }; @@ -97,10 +105,7 @@ fn queries<'a>() -> Queries< query = query.filter(not_removed_or_deleted); } - // Hide local only communities from unauthenticated users - if my_person_id.is_none() { - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); - } + query = visible_communities_only(my_person_id, query); query.first(&mut conn).await }; @@ -108,14 +113,14 @@ fn queries<'a>() -> Queries< let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move { use SortType::*; - let my_person_id = options.local_user.map(|l| l.person_id); - // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); + let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); - let mut query = all_joins(community::table.into_boxed(), my_person_id) - .left_join(local_user::table.on(local_user::person_id.eq(person_id_join))) - .select(selection); + let mut query = all_joins( + community::table.into_boxed(), + options.local_user.person_id(), + ) + .select(selection); if let Some(search_term) = options.search_term { let searcher = fuzzy_search(&search_term); @@ -154,7 +159,7 @@ fn queries<'a>() -> Queries< if let Some(listing_type) = options.listing_type { query = match listing_type { - ListingType::Subscribed => query.filter(community_follower::pending.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)), + ListingType::Subscribed => query.filter(community_follower::pending.is_not_null()), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ ListingType::Local => query.filter(community::local.eq(true)), _ => query, }; @@ -162,21 +167,14 @@ fn queries<'a>() -> Queries< // Don't show blocked communities and communities on blocked instances. nsfw communities are // also hidden (based on profile setting) - if options.local_user.is_some() { - query = query.filter(instance_block::person_id.is_null()); - query = query.filter(community_block::person_id.is_null()); - query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true))); - } else { - // No person in request, only show nsfw communities if show_nsfw is passed into request or if - // site has content warning. - let has_content_warning = site.content_warning.is_some(); - if !options.show_nsfw && !has_content_warning { - query = query.filter(community::nsfw.eq(false)); - } - // Hide local only communities from unauthenticated users - query = query.filter(community::visibility.eq(CommunityVisibility::Public)); + query = query.filter(instance_block::person_id.is_null()); + query = query.filter(community_block::person_id.is_null()); + if !(options.local_user.show_nsfw(site) || options.show_nsfw) { + query = query.filter(community::nsfw.eq(false)); } + query = visible_communities_only(options.local_user.person_id(), query); + let (limit, offset) = limit_and_offset(options.page, options.limit)?; query .limit(limit) @@ -286,11 +284,7 @@ mod tests { let person_name = "tegan".to_string(); - let new_person = PersonInsertForm::builder() - .name(person_name.clone()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_person = PersonInsertForm::test_form(inserted_instance.id, &person_name); let inserted_person = Person::create(pool, &new_person).await.unwrap(); diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 65c0bd0e6..d6fd7363d 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,6 +1,6 @@ use crate::structs::PersonMentionView; use diesel::{ - dsl::exists, + dsl::{exists, not}, pg::Pg, result::Error, sql_types, @@ -31,6 +31,7 @@ use lemmy_db_schema::{ person_mention, post, }, + source::local_user::LocalUser, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, CommentSortType, }; @@ -192,6 +193,8 @@ fn queries<'a>() -> Queries< }; let list = move |mut conn: DbConn<'a>, options: PersonMentionQuery| async move { + // These filters need to be kept in sync with the filters in + // PersonMentionView::get_unread_mentions() let mut query = all_joins(person_mention::table.into_boxed(), options.my_person_id); if let Some(recipient_id) = options.recipient_id { @@ -203,7 +206,7 @@ fn queries<'a>() -> Queries< } if !options.show_bot_accounts { - query = query.filter(person::bot_account.eq(false)); + query = query.filter(not(person::bot_account)); }; query = match options.sort.unwrap_or(CommentSortType::Hot) { @@ -216,6 +219,11 @@ fn queries<'a>() -> Queries< CommentSortType::Top => query.order_by(comment_aggregates::score.desc()), }; + // Don't show mentions from blocked persons + if let Some(my_person_id) = options.my_person_id { + query = query.filter(not(is_creator_blocked(my_person_id))); + } + let (limit, offset) = limit_and_offset(options.page, options.limit)?; query @@ -242,14 +250,32 @@ impl PersonMentionView { /// Gets the number of unread mentions pub async fn get_unread_mentions( pool: &mut DbPool<'_>, - my_person_id: PersonId, + local_user: &LocalUser, ) -> Result { use diesel::dsl::count; let conn = &mut get_conn(pool).await?; - person_mention::table + let mut query = person_mention::table .inner_join(comment::table) - .filter(person_mention::recipient_id.eq(my_person_id)) + .left_join( + person_block::table.on( + comment::creator_id + .eq(person_block::target_id) + .and(person_block::person_id.eq(local_user.person_id)), + ), + ) + .inner_join(person::table.on(comment::creator_id.eq(person::id))) + .into_boxed(); + + // These filters need to be kept in sync with the filters in queries().list() + if !local_user.show_bot_accounts { + query = query.filter(not(person::bot_account)); + } + + query + // Don't count replies from blocked users + .filter(person_block::person_id.is_null()) + .filter(person_mention::recipient_id.eq(local_user.person_id)) .filter(person_mention::read.eq(false)) .filter(comment::deleted.eq(false)) .filter(comment::removed.eq(false)) @@ -259,7 +285,7 @@ impl PersonMentionView { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct PersonMentionQuery { pub my_person_id: Option, pub recipient_id: Option, @@ -275,3 +301,174 @@ impl PersonMentionQuery { queries().list(pool, self).await } } + +#[cfg(test)] +#[allow(clippy::indexing_slicing)] +mod tests { + + use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView}; + use lemmy_db_schema::{ + source::{ + comment::{Comment, CommentInsertForm}, + community::{Community, CommunityInsertForm}, + instance::Instance, + local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm}, + person::{Person, PersonInsertForm, PersonUpdateForm}, + person_block::{PersonBlock, PersonBlockForm}, + person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm}, + post::{Post, PostInsertForm}, + }, + traits::{Blockable, Crud}, + utils::build_db_pool_for_tests, + }; + use lemmy_db_views::structs::LocalUserView; + use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use pretty_assertions::assert_eq; + use serial_test::serial; + + #[tokio::test] + #[serial] + async fn test_crud() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + + let new_person = PersonInsertForm::test_form(inserted_instance.id, "terrylake"); + + let inserted_person = Person::create(pool, &new_person).await?; + + let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient"); + + let inserted_recipient = Person::create(pool, &recipient_form).await?; + let recipient_id = inserted_recipient.id; + + let recipient_local_user = + LocalUser::create(pool, &LocalUserInsertForm::test_form(recipient_id), vec![]).await?; + + let new_community = CommunityInsertForm::builder() + .name("test community lake".to_string()) + .title("nada".to_owned()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_community = Community::create(pool, &new_community).await?; + + let new_post = PostInsertForm::builder() + .name("A test post".into()) + .creator_id(inserted_person.id) + .community_id(inserted_community.id) + .build(); + + let inserted_post = Post::create(pool, &new_post).await?; + + let comment_form = CommentInsertForm::builder() + .content("A test comment".into()) + .creator_id(inserted_person.id) + .post_id(inserted_post.id) + .build(); + + let inserted_comment = Comment::create(pool, &comment_form, None).await?; + + let person_mention_form = PersonMentionInsertForm { + recipient_id: inserted_recipient.id, + comment_id: inserted_comment.id, + read: None, + }; + + let inserted_mention = PersonMention::create(pool, &person_mention_form).await?; + + let expected_mention = PersonMention { + id: inserted_mention.id, + recipient_id: inserted_mention.recipient_id, + comment_id: inserted_mention.comment_id, + read: false, + published: inserted_mention.published, + }; + + let read_mention = PersonMention::read(pool, inserted_mention.id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; + + let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) }; + let updated_mention = + PersonMention::update(pool, inserted_mention.id, &person_mention_update_form).await?; + + // Test to make sure counts and blocks work correctly + let unread_mentions = + PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?; + + let query = PersonMentionQuery { + recipient_id: Some(recipient_id), + my_person_id: Some(recipient_id), + sort: None, + unread_only: false, + show_bot_accounts: true, + page: None, + limit: None, + }; + let mentions = query.clone().list(pool).await?; + assert_eq!(1, unread_mentions); + assert_eq!(1, mentions.len()); + + // Block the person, and make sure these counts are now empty + let block_form = PersonBlockForm { + person_id: recipient_id, + target_id: inserted_person.id, + }; + PersonBlock::block(pool, &block_form).await?; + + let unread_mentions_after_block = + PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?; + let mentions_after_block = query.clone().list(pool).await?; + assert_eq!(0, unread_mentions_after_block); + assert_eq!(0, mentions_after_block.len()); + + // Unblock user so we can reuse the same person + PersonBlock::unblock(pool, &block_form).await?; + + // Turn Terry into a bot account + let person_update_form = PersonUpdateForm { + bot_account: Some(true), + ..Default::default() + }; + Person::update(pool, inserted_person.id, &person_update_form).await?; + + let recipient_local_user_update_form = LocalUserUpdateForm { + show_bot_accounts: Some(false), + ..Default::default() + }; + LocalUser::update( + pool, + recipient_local_user.id, + &recipient_local_user_update_form, + ) + .await?; + let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + + let unread_mentions_after_hide_bots = + PersonMentionView::get_unread_mentions(pool, &recipient_local_user_view.local_user).await?; + + let mut query_without_bots = query.clone(); + query_without_bots.show_bot_accounts = false; + let replies_after_hide_bots = query_without_bots.list(pool).await?; + assert_eq!(0, unread_mentions_after_hide_bots); + assert_eq!(0, replies_after_hide_bots.len()); + + Comment::delete(pool, inserted_comment.id).await?; + Post::delete(pool, inserted_post.id).await?; + Community::delete(pool, inserted_community.id).await?; + Person::delete(pool, inserted_person.id).await?; + Person::delete(pool, inserted_recipient.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + assert_eq!(expected_mention, read_mention); + assert_eq!(expected_mention, inserted_mention); + assert_eq!(expected_mention, updated_mention); + + Ok(()) + } +} diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 5734bc812..98a0ca38d 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -191,12 +191,10 @@ mod tests { async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; - let alice_form = PersonInsertForm::builder() - .name("alice".to_string()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .local(Some(true)) - .build(); + let alice_form = PersonInsertForm { + local: Some(true), + ..PersonInsertForm::test_form(inserted_instance.id, "alice") + }; let alice = Person::create(pool, &alice_form).await?; let alice_local_user_form = LocalUserInsertForm::builder() .person_id(alice.id) @@ -204,13 +202,11 @@ mod tests { .build(); let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?; - let bob_form = PersonInsertForm::builder() - .name("bob".to_string()) - .bot_account(Some(true)) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .local(Some(false)) - .build(); + let bob_form = PersonInsertForm { + bot_account: Some(true), + local: Some(false), + ..PersonInsertForm::test_form(inserted_instance.id, "bob") + }; let bob = Person::create(pool, &bob_form).await?; let bob_local_user_form = LocalUserInsertForm::builder() .person_id(bob.id) diff --git a/crates/federate/Cargo.toml b/crates/federate/Cargo.toml index 9ccd8adc3..2405d3af0 100644 --- a/crates/federate/Cargo.toml +++ b/crates/federate/Cargo.toml @@ -19,6 +19,7 @@ lemmy_api_common.workspace = true lemmy_apub.workspace = true lemmy_db_schema = { workspace = true, features = ["full"] } lemmy_db_views_actor.workspace = true +lemmy_utils.workspace = true activitypub_federation.workspace = true anyhow.workspace = true @@ -32,4 +33,7 @@ serde_json.workspace = true tokio = { workspace = true, features = ["full"] } tracing.workspace = true moka.workspace = true -tokio-util = "0.7.10" +tokio-util = "0.7.11" + +[dev-dependencies] +serial_test = { workspace = true } diff --git a/crates/federate/src/lib.rs b/crates/federate/src/lib.rs index a4dc49536..21b9229b5 100644 --- a/crates/federate/src/lib.rs +++ b/crates/federate/src/lib.rs @@ -1,19 +1,20 @@ use crate::{util::CancellableTask, worker::InstanceWorker}; use activitypub_federation::config::FederationConfig; -use chrono::{Local, Timelike}; -use lemmy_api_common::{context::LemmyContext, federate_retry_sleep_duration}; -use lemmy_db_schema::{ - newtypes::InstanceId, - source::{federation_queue_state::FederationQueueState, instance::Instance}, - utils::{ActualDbPool, DbPool}, -}; +use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::{newtypes::InstanceId, source::instance::Instance}; +use lemmy_utils::error::LemmyResult; +use stats::receive_print_stats; use std::{collections::HashMap, time::Duration}; use tokio::{ - sync::mpsc::{unbounded_channel, UnboundedReceiver}, + sync::mpsc::{unbounded_channel, UnboundedSender}, + task::JoinHandle, time::sleep, }; use tokio_util::sync::CancellationToken; +use tracing::info; +use util::FederationQueueStateWithDomain; +mod stats; mod util; mod worker; @@ -31,168 +32,319 @@ pub struct Opts { pub process_index: i32, } -async fn start_stop_federation_workers( +pub struct SendManager { opts: Opts, - pool: ActualDbPool, - federation_config: FederationConfig, - cancel: CancellationToken, -) -> anyhow::Result<()> { - let mut workers = HashMap::::new(); + workers: HashMap, + context: FederationConfig, + stats_sender: UnboundedSender, + exit_print: JoinHandle<()>, +} - let (stats_sender, stats_receiver) = unbounded_channel(); - let exit_print = tokio::spawn(receive_print_stats(pool.clone(), stats_receiver)); - let pool2 = &mut DbPool::Pool(&pool); - let process_index = opts.process_index - 1; - let local_domain = federation_config.settings().get_hostname_without_port()?; - loop { - let mut total_count = 0; - let mut dead_count = 0; - let mut disallowed_count = 0; - for (instance, allowed, is_dead) in - Instance::read_federated_with_blocked_and_dead(pool2).await? - { - if instance.domain == local_domain { - continue; +impl SendManager { + fn new(opts: Opts, context: FederationConfig) -> Self { + assert!(opts.process_count > 0); + assert!(opts.process_index > 0); + assert!(opts.process_index <= opts.process_count); + + let (stats_sender, stats_receiver) = unbounded_channel(); + Self { + opts, + workers: HashMap::new(), + stats_sender, + exit_print: tokio::spawn(receive_print_stats( + context.inner_pool().clone(), + stats_receiver, + )), + context, + } + } + + pub fn run(opts: Opts, context: FederationConfig) -> CancellableTask { + CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |cancel| { + let opts = opts.clone(); + let context = context.clone(); + let mut manager = Self::new(opts, context); + async move { + let result = manager.do_loop(cancel).await; + // the loop function will only return if there is (a) an internal error (e.g. db connection + // failure) or (b) it was cancelled from outside. + if let Err(e) = result { + // don't let this error bubble up, just log it, so the below cancel function will run + // regardless + tracing::error!("SendManager failed: {e}"); + } + // cancel all the dependent workers as well to ensure they don't get orphaned and keep + // running. + manager.cancel().await?; + LemmyResult::Ok(()) + // if the task was not intentionally cancelled, then this whole lambda will be run again by + // CancellableTask after this } - if instance.id.inner() % opts.process_count != process_index { - continue; - } - total_count += 1; - if !allowed { - disallowed_count += 1; - } - if is_dead { - dead_count += 1; - } - let should_federate = allowed && !is_dead; - if should_federate { - if workers.contains_key(&instance.id) { - // worker already running + }) + } + + async fn do_loop(&mut self, cancel: CancellationToken) -> LemmyResult<()> { + let process_index = self.opts.process_index - 1; + info!( + "Starting federation workers for process count {} and index {}", + self.opts.process_count, process_index + ); + let local_domain = self.context.settings().get_hostname_without_port()?; + let mut pool = self.context.pool(); + loop { + let mut total_count = 0; + let mut dead_count = 0; + let mut disallowed_count = 0; + for (instance, allowed, is_dead) in + Instance::read_federated_with_blocked_and_dead(&mut pool).await? + { + if instance.domain == local_domain { continue; } - // create new worker - let config = federation_config.clone(); - let stats_sender = stats_sender.clone(); - let pool = pool.clone(); - workers.insert( - instance.id, - CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| { - let instance = instance.clone(); - let req_data = config.clone().to_request_data(); - let stats_sender = stats_sender.clone(); - let pool = pool.clone(); - async move { - InstanceWorker::init_and_loop( - instance, - req_data, - &mut DbPool::Pool(&pool), - stop, - stats_sender, - ) - .await + if instance.id.inner() % self.opts.process_count != process_index { + continue; + } + total_count += 1; + if !allowed { + disallowed_count += 1; + } + if is_dead { + dead_count += 1; + } + let should_federate = allowed && !is_dead; + if should_federate { + if self.workers.contains_key(&instance.id) { + // worker already running + continue; + } + // create new worker + let context = self.context.clone(); + let stats_sender = self.stats_sender.clone(); + self.workers.insert( + instance.id, + CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| { + // if the instance worker ends unexpectedly due to internal/db errors, this lambda is rerun by cancellabletask. + let instance = instance.clone(); + let req_data = context.to_request_data(); + let stats_sender = stats_sender.clone(); + async move { + InstanceWorker::init_and_loop( + instance, + req_data, + stop, + stats_sender, + ) + .await + } + }), + ); + } else if !should_federate { + if let Some(worker) = self.workers.remove(&instance.id) { + if let Err(e) = worker.cancel().await { + tracing::error!("error stopping worker: {e}"); } - }), - ); - } else if !should_federate { - if let Some(worker) = workers.remove(&instance.id) { - if let Err(e) = worker.cancel().await { - tracing::error!("error stopping worker: {e}"); } } } - } - let worker_count = workers.len(); - tracing::info!("Federating to {worker_count}/{total_count} instances ({dead_count} dead, {disallowed_count} disallowed)"); - tokio::select! { - () = sleep(INSTANCES_RECHECK_DELAY) => {}, - _ = cancel.cancelled() => { break; } - } - } - drop(stats_sender); - tracing::warn!( - "Waiting for {} workers ({:.2?} max)", - workers.len(), - WORKER_EXIT_TIMEOUT - ); - // the cancel futures need to be awaited concurrently for the shutdown processes to be triggered concurrently - futures::future::join_all(workers.into_values().map(util::CancellableTask::cancel)).await; - exit_print.await?; - Ok(()) -} - -/// starts and stops federation workers depending on which instances are on db -/// await the returned future to stop/cancel all workers gracefully -pub fn start_stop_federation_workers_cancellable( - opts: Opts, - pool: ActualDbPool, - config: FederationConfig, -) -> CancellableTask { - CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| { - let opts = opts.clone(); - let pool = pool.clone(); - let config = config.clone(); - async move { start_stop_federation_workers(opts, pool, config, stop).await } - }) -} - -/// every 60s, print the state for every instance. exits if the receiver is done (all senders dropped) -async fn receive_print_stats( - pool: ActualDbPool, - mut receiver: UnboundedReceiver<(String, FederationQueueState)>, -) { - let pool = &mut DbPool::Pool(&pool); - let mut printerval = tokio::time::interval(Duration::from_secs(60)); - printerval.tick().await; // skip first - let mut stats = HashMap::new(); - loop { - tokio::select! { - ele = receiver.recv() => { - let Some((domain, ele)) = ele else { - print_stats(pool, &stats).await; - return; - }; - stats.insert(domain, ele); - }, - _ = printerval.tick() => { - print_stats(pool, &stats).await; + let worker_count = self.workers.len(); + tracing::info!("Federating to {worker_count}/{total_count} instances ({dead_count} dead, {disallowed_count} disallowed)"); + tokio::select! { + () = sleep(INSTANCES_RECHECK_DELAY) => {}, + _ = cancel.cancelled() => { return Ok(()) } } } } + + pub async fn cancel(self) -> LemmyResult<()> { + drop(self.stats_sender); + tracing::warn!( + "Waiting for {} workers ({:.2?} max)", + self.workers.len(), + WORKER_EXIT_TIMEOUT + ); + // the cancel futures need to be awaited concurrently for the shutdown processes to be triggered + // concurrently + futures::future::join_all( + self + .workers + .into_values() + .map(util::CancellableTask::cancel), + ) + .await; + self.exit_print.await?; + Ok(()) + } } -async fn print_stats(pool: &mut DbPool<'_>, stats: &HashMap) { - let last_id = crate::util::get_latest_activity_id(pool).await; - let Ok(last_id) = last_id else { - tracing::error!("could not get last id"); - return; +#[cfg(test)] +#[allow(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] +mod test { + + use super::*; + use activitypub_federation::config::Data; + use chrono::DateTime; + use lemmy_db_schema::source::{ + federation_allowlist::FederationAllowList, + federation_blocklist::FederationBlockList, + instance::InstanceForm, }; - // it's expected that the values are a bit out of date, everything < SAVE_STATE_EVERY should be considered up to date - tracing::info!( - "Federation state as of {}:", - Local::now() - .with_nanosecond(0) - .expect("0 is valid nanos") - .to_rfc3339() - ); - // todo: more stats (act/sec, avg http req duration) - let mut ok_count = 0; - let mut behind_count = 0; - for (domain, stat) in stats { - let behind = last_id.0 - stat.last_successful_id.map(|e| e.0).unwrap_or(0); - if stat.fail_count > 0 { - tracing::info!( - "{}: Warning. {} behind, {} consecutive fails, current retry delay {:.2?}", - domain, - behind, - stat.fail_count, - federate_retry_sleep_duration(stat.fail_count) - ); - } else if behind > 0 { - tracing::debug!("{}: Ok. {} activities behind", domain, behind); - behind_count += 1; - } else { - ok_count += 1; + use lemmy_utils::error::LemmyError; + use serial_test::serial; + use std::{ + collections::HashSet, + sync::{Arc, Mutex}, + }; + use tokio::spawn; + + struct TestData { + send_manager: SendManager, + context: Data, + instances: Vec, + } + impl TestData { + async fn init(process_count: i32, process_index: i32) -> LemmyResult { + let context = LemmyContext::init_test_context().await; + let opts = Opts { + process_count, + process_index, + }; + let federation_config = FederationConfig::builder() + .domain("local.com") + .app_data(context.clone()) + .build() + .await?; + + let pool = &mut context.pool(); + let instances = vec![ + Instance::read_or_create(pool, "alpha.com".to_string()).await?, + Instance::read_or_create(pool, "beta.com".to_string()).await?, + Instance::read_or_create(pool, "gamma.com".to_string()).await?, + ]; + + let send_manager = SendManager::new(opts, federation_config); + Ok(Self { + send_manager, + context, + instances, + }) + } + + async fn run(&mut self) -> LemmyResult<()> { + // start it and cancel after workers are running + let cancel = CancellationToken::new(); + let cancel_ = cancel.clone(); + spawn(async move { + sleep(Duration::from_millis(100)).await; + cancel_.cancel(); + }); + self.send_manager.do_loop(cancel.clone()).await?; + Ok(()) + } + + async fn cleanup(self) -> LemmyResult<()> { + self.send_manager.cancel().await?; + Instance::delete_all(&mut self.context.pool()).await?; + Ok(()) } } - tracing::info!("{ok_count} others up to date. {behind_count} instances behind."); + + /// Basic test with default params and only active/allowed instances + #[tokio::test] + #[serial] + async fn test_send_manager() -> LemmyResult<()> { + let mut data = TestData::init(1, 1).await?; + + data.run().await?; + assert_eq!(3, data.send_manager.workers.len()); + let workers: HashSet<_> = data.send_manager.workers.keys().cloned().collect(); + let instances: HashSet<_> = data.instances.iter().map(|i| i.id).collect(); + assert_eq!(instances, workers); + + data.cleanup().await?; + Ok(()) + } + + /// Running with multiple processes should start correct workers + #[tokio::test] + #[serial] + async fn test_send_manager_processes() -> LemmyResult<()> { + let active = Arc::new(Mutex::new(vec![])); + let execute = |count, index, active: Arc>>| async move { + let mut data = TestData::init(count, index).await?; + data.run().await?; + assert_eq!(1, data.send_manager.workers.len()); + for k in data.send_manager.workers.keys() { + active.lock().unwrap().push(*k); + } + data.cleanup().await?; + Ok::<(), LemmyError>(()) + }; + execute(3, 1, active.clone()).await?; + execute(3, 2, active.clone()).await?; + execute(3, 3, active.clone()).await?; + + // Should run exactly three workers + assert_eq!(3, active.lock().unwrap().len()); + + Ok(()) + } + + /// Use blocklist, should not send to blocked instances + #[tokio::test] + #[serial] + async fn test_send_manager_blocked() -> LemmyResult<()> { + let mut data = TestData::init(1, 1).await?; + + let domain = data.instances[0].domain.clone(); + FederationBlockList::replace(&mut data.context.pool(), Some(vec![domain])).await?; + data.run().await?; + let workers = &data.send_manager.workers; + assert_eq!(2, workers.len()); + assert!(workers.contains_key(&data.instances[1].id)); + assert!(workers.contains_key(&data.instances[2].id)); + + data.cleanup().await?; + Ok(()) + } + + /// Use allowlist, should only send to allowed instance + #[tokio::test] + #[serial] + async fn test_send_manager_allowed() -> LemmyResult<()> { + let mut data = TestData::init(1, 1).await?; + + let domain = data.instances[0].domain.clone(); + FederationAllowList::replace(&mut data.context.pool(), Some(vec![domain])).await?; + data.run().await?; + let workers = &data.send_manager.workers; + assert_eq!(1, workers.len()); + assert!(workers.contains_key(&data.instances[0].id)); + + data.cleanup().await?; + Ok(()) + } + + /// Mark instance as dead, there should be no worker created for it + #[tokio::test] + #[serial] + async fn test_send_manager_dead() -> LemmyResult<()> { + let mut data = TestData::init(1, 1).await?; + + let instance = &data.instances[0]; + let form = InstanceForm::builder() + .domain(instance.domain.clone()) + .updated(DateTime::from_timestamp(0, 0)) + .build(); + Instance::update(&mut data.context.pool(), instance.id, form).await?; + + data.run().await?; + let workers = &data.send_manager.workers; + assert_eq!(2, workers.len()); + assert!(workers.contains_key(&data.instances[1].id)); + assert!(workers.contains_key(&data.instances[2].id)); + + data.cleanup().await?; + Ok(()) + } } diff --git a/crates/federate/src/stats.rs b/crates/federate/src/stats.rs new file mode 100644 index 000000000..f927f6ddf --- /dev/null +++ b/crates/federate/src/stats.rs @@ -0,0 +1,83 @@ +use crate::util::{get_latest_activity_id, FederationQueueStateWithDomain}; +use chrono::Local; +use lemmy_api_common::federate_retry_sleep_duration; +use lemmy_db_schema::{ + newtypes::InstanceId, + utils::{ActualDbPool, DbPool}, +}; +use lemmy_utils::error::LemmyResult; +use std::{collections::HashMap, time::Duration}; +use tokio::{sync::mpsc::UnboundedReceiver, time::interval}; +use tracing::{debug, info, warn}; + +/// every 60s, print the state for every instance. exits if the receiver is done (all senders +/// dropped) +pub(crate) async fn receive_print_stats( + pool: ActualDbPool, + mut receiver: UnboundedReceiver, +) { + let pool = &mut DbPool::Pool(&pool); + let mut printerval = interval(Duration::from_secs(60)); + let mut stats = HashMap::new(); + loop { + tokio::select! { + ele = receiver.recv() => { + match ele { + // update stats for instance + Some(ele) => {stats.insert(ele.state.instance_id, ele);}, + // receiver closed, print stats and exit + None => { + print_stats(pool, &stats).await; + return; + } + } + }, + _ = printerval.tick() => { + print_stats(pool, &stats).await; + } + } + } +} + +async fn print_stats( + pool: &mut DbPool<'_>, + stats: &HashMap, +) { + let res = print_stats_with_error(pool, stats).await; + if let Err(e) = res { + warn!("Failed to print stats: {e}"); + } +} + +async fn print_stats_with_error( + pool: &mut DbPool<'_>, + stats: &HashMap, +) -> LemmyResult<()> { + let last_id = get_latest_activity_id(pool).await?; + + // it's expected that the values are a bit out of date, everything < SAVE_STATE_EVERY should be + // considered up to date + info!("Federation state as of {}:", Local::now().to_rfc3339()); + // todo: more stats (act/sec, avg http req duration) + let mut ok_count = 0; + let mut behind_count = 0; + for ele in stats.values() { + let stat = &ele.state; + let domain = &ele.domain; + let behind = last_id.0 - stat.last_successful_id.map(|e| e.0).unwrap_or(0); + if stat.fail_count > 0 { + info!( + "{domain}: Warning. {behind} behind, {} consecutive fails, current retry delay {:.2?}", + stat.fail_count, + federate_retry_sleep_duration(stat.fail_count) + ); + } else if behind > 0 { + debug!("{}: Ok. {} activities behind", domain, behind); + behind_count += 1; + } else { + ok_count += 1; + } + } + info!("{ok_count} others up to date. {behind_count} instances behind."); + Ok(()) +} diff --git a/crates/federate/src/util.rs b/crates/federate/src/util.rs index b057ab0fe..60361c3c9 100644 --- a/crates/federate/src/util.rs +++ b/crates/federate/src/util.rs @@ -11,6 +11,7 @@ use lemmy_db_schema::{ source::{ activity::{ActorType, SentActivity}, community::Community, + federation_queue_state::FederationQueueState, person::Person, site::Site, }, @@ -26,7 +27,8 @@ use tokio::{task::JoinHandle, time::sleep}; use tokio_util::sync::CancellationToken; /// Decrease the delays of the federation queue. -/// Should only be used for federation tests since it significantly increases CPU and DB load of the federation queue. +/// Should only be used for federation tests since it significantly increases CPU and DB load of the +/// federation queue. pub(crate) static LEMMY_TEST_FAST_FEDERATION: Lazy = Lazy::new(|| { std::env::var("LEMMY_TEST_FAST_FEDERATION") .map(|s| !s.is_empty()) @@ -35,9 +37,10 @@ pub(crate) static LEMMY_TEST_FAST_FEDERATION: Lazy = Lazy::new(|| { /// Recheck for new federation work every n seconds. /// -/// When the queue is processed faster than new activities are added and it reaches the current time with an empty batch, -/// this is the delay the queue waits before it checks if new activities have been added to the sent_activities table. -/// This delay is only applied if no federated activity happens during sending activities of the last batch. +/// When the queue is processed faster than new activities are added and it reaches the current time +/// with an empty batch, this is the delay the queue waits before it checks if new activities have +/// been added to the sent_activities table. This delay is only applied if no federated activity +/// happens during sending activities of the last batch. pub(crate) static WORK_FINISHED_RECHECK_DELAY: Lazy = Lazy::new(|| { if *LEMMY_TEST_FAST_FEDERATION { Duration::from_millis(100) @@ -47,19 +50,21 @@ pub(crate) static WORK_FINISHED_RECHECK_DELAY: Lazy = Lazy::new(|| { }); /// A task that will be run in an infinite loop, unless it is cancelled. -/// If the task exits without being cancelled, an error will be logged and the task will be restarted. +/// If the task exits without being cancelled, an error will be logged and the task will be +/// restarted. pub struct CancellableTask { f: Pin> + Send + 'static>>, } impl CancellableTask { /// spawn a task but with graceful shutdown - pub fn spawn( + pub fn spawn( timeout: Duration, task: impl Fn(CancellationToken) -> F + Send + 'static, ) -> CancellableTask where F: Future + Send + 'static, + R: Send + Debug + 'static, { let stop = CancellationToken::new(); let stop2 = stop.clone(); @@ -79,13 +84,12 @@ impl CancellableTask { stop.cancel(); tokio::select! { r = task => { - r.context("could not join")?; - Ok(()) + r.context("CancellableTask failed to cancel cleanly, returned error")?; + Ok(()) }, _ = sleep(timeout) => { abort.abort(); - tracing::warn!("Graceful shutdown timed out, aborting task"); - Err(anyhow!("task aborted due to timeout")) + Err(anyhow!("CancellableTask aborted due to shutdown timeout")) } } }), @@ -185,3 +189,10 @@ pub(crate) async fn get_latest_activity_id(pool: &mut DbPool<'_>) -> Result = Lazy::new(|| { if *LEMMY_TEST_FAST_FEDERATION { chrono::TimeDelta::try_seconds(1).expect("TimeDelta out of bounds") @@ -53,8 +60,9 @@ static FOLLOW_ADDITIONS_RECHECK_DELAY: Lazy = Lazy::new(|| { chrono::TimeDelta::try_minutes(2).expect("TimeDelta out of bounds") } }); -/// The same as FOLLOW_ADDITIONS_RECHECK_DELAY, but triggering when the last person on an instance unfollows a specific remote community. -/// This is expected to happen pretty rarely and updating it in a timely manner is not too important. +/// The same as FOLLOW_ADDITIONS_RECHECK_DELAY, but triggering when the last person on an instance +/// unfollows a specific remote community. This is expected to happen pretty rarely and updating it +/// in a timely manner is not too important. static FOLLOW_REMOVALS_RECHECK_DELAY: Lazy = Lazy::new(|| chrono::TimeDelta::try_hours(1).expect("TimeDelta out of bounds")); pub(crate) struct InstanceWorker { @@ -68,7 +76,7 @@ pub(crate) struct InstanceWorker { followed_communities: HashMap>, stop: CancellationToken, context: Data, - stats_sender: UnboundedSender<(String, FederationQueueState)>, + stats_sender: UnboundedSender, last_full_communities_fetch: DateTime, last_incremental_communities_fetch: DateTime, state: FederationQueueState, @@ -79,11 +87,11 @@ impl InstanceWorker { pub(crate) async fn init_and_loop( instance: Instance, context: Data, - pool: &mut DbPool<'_>, // in theory there's a ref to the pool in context, but i couldn't get that to work wrt lifetimes stop: CancellationToken, - stats_sender: UnboundedSender<(String, FederationQueueState)>, + stats_sender: UnboundedSender, ) -> Result<(), anyhow::Error> { - let state = FederationQueueState::load(pool, instance.id).await?; + let mut pool = context.pool(); + let state = FederationQueueState::load(&mut pool, instance.id).await?; let mut worker = InstanceWorker { instance, site_loaded: false, @@ -97,30 +105,29 @@ impl InstanceWorker { state, last_state_insert: Utc.timestamp_nanos(0), }; - worker.loop_until_stopped(pool).await + worker.loop_until_stopped().await } /// loop fetch new activities from db and send them to the inboxes of the given instances - /// this worker only returns if (a) there is an internal error or (b) the cancellation token is cancelled (graceful exit) - pub(crate) async fn loop_until_stopped( - &mut self, - pool: &mut DbPool<'_>, - ) -> Result<(), anyhow::Error> { + /// this worker only returns if (a) there is an internal error or (b) the cancellation token is + /// cancelled (graceful exit) + pub(crate) async fn loop_until_stopped(&mut self) -> Result<(), anyhow::Error> { + debug!("Starting federation worker for {}", self.instance.domain); let save_state_every = chrono::Duration::from_std(SAVE_STATE_EVERY_TIME).expect("not negative"); - self.update_communities(pool).await?; + self.update_communities().await?; self.initial_fail_sleep().await?; while !self.stop.is_cancelled() { - self.loop_batch(pool).await?; + self.loop_batch().await?; if self.stop.is_cancelled() { break; } if (Utc::now() - self.last_state_insert) > save_state_every { - self.save_and_send_state(pool).await?; + self.save_and_send_state().await?; } - self.update_communities(pool).await?; + self.update_communities().await?; } // final update of state in db - self.save_and_send_state(pool).await?; + self.save_and_send_state().await?; Ok(()) } @@ -145,19 +152,29 @@ impl InstanceWorker { Ok(()) } /// send out a batch of CHECK_SAVE_STATE_EVERY_IT activities - async fn loop_batch(&mut self, pool: &mut DbPool<'_>) -> Result<()> { - let latest_id = get_latest_activity_id(pool).await?; + async fn loop_batch(&mut self) -> Result<()> { + let latest_id = get_latest_activity_id(&mut self.context.pool()).await?; let mut id = if let Some(id) = self.state.last_successful_id { id } else { - // this is the initial creation (instance first seen) of the federation queue for this instance + // this is the initial creation (instance first seen) of the federation queue for this + // instance + // skip all past activities: self.state.last_successful_id = Some(latest_id); // save here to ensure it's not read as 0 again later if no activities have happened - self.save_and_send_state(pool).await?; + self.save_and_send_state().await?; latest_id }; if id >= latest_id { + if id > latest_id { + tracing::error!( + "{}: last successful id {} is higher than latest id {} in database (did the db get cleared?)", + self.instance.domain, + id.0, + latest_id.0 + ); + } // no more work to be done, wait before rechecking tokio::select! { () = sleep(*WORK_FINISHED_RECHECK_DELAY) => {}, @@ -172,19 +189,18 @@ impl InstanceWorker { { id = ActivityId(id.0 + 1); processed_activities += 1; - let Some(ele) = get_activity_cached(pool, id) + let Some(ele) = get_activity_cached(&mut self.context.pool(), id) .await .context("failed reading activity from db")? else { - tracing::debug!("{}: {:?} does not exist", self.instance.domain, id); + debug!("{}: {:?} does not exist", self.instance.domain, id); self.state.last_successful_id = Some(id); continue; }; - if let Err(e) = self.send_retry_loop(pool, &ele.0, &ele.1).await { - tracing::warn!( + if let Err(e) = self.send_retry_loop(&ele.0, &ele.1).await { + warn!( "sending {} errored internally, skipping activity: {:?}", - ele.0.ap_id, - e + ele.0.ap_id, e ); } if self.stop.is_cancelled() { @@ -202,16 +218,15 @@ impl InstanceWorker { // and will return an error if an internal error occurred (send errors cause an infinite loop) async fn send_retry_loop( &mut self, - pool: &mut DbPool<'_>, activity: &SentActivity, object: &SharedInboxActivities, ) -> Result<()> { let inbox_urls = self - .get_inbox_urls(pool, activity) + .get_inbox_urls(activity) .await .context("failed figuring out inbox urls")?; if inbox_urls.is_empty() { - tracing::debug!("{}: {:?} no inboxes", self.instance.domain, activity.id); + trace!("{}: {:?} no inboxes", self.instance.domain, activity.id); self.state.last_successful_id = Some(activity.id); self.state.last_successful_published_time = Some(activity.published); return Ok(()); @@ -219,7 +234,7 @@ impl InstanceWorker { let Some(actor_apub_id) = &activity.actor_apub_id else { return Ok(()); // activity was inserted before persistent queue was activated }; - let actor = get_actor_cached(pool, activity.actor_type, actor_apub_id) + let actor = get_actor_cached(&mut self.context.pool(), activity.actor_type, actor_apub_id) .await .context("failed getting actor instance (was it marked deleted / removed?)")?; @@ -229,18 +244,16 @@ impl InstanceWorker { SendActivityTask::prepare(&object, actor.as_ref(), inbox_urls, &self.context).await?; for task in requests { // usually only one due to shared inbox - tracing::debug!("sending out {}", task); + trace!("sending out {}", task); while let Err(e) = task.sign_and_send(&self.context).await { self.state.fail_count += 1; self.state.last_retry = Some(Utc::now()); let retry_delay: Duration = federate_retry_sleep_duration(self.state.fail_count); - tracing::info!( + info!( "{}: retrying {:?} attempt {} with delay {retry_delay:.2?}. ({e})", - self.instance.domain, - activity.id, - self.state.fail_count + self.instance.domain, activity.id, self.state.fail_count ); - self.save_and_send_state(pool).await?; + self.save_and_send_state().await?; tokio::select! { () = sleep(retry_delay) => {}, () = self.stop.cancelled() => { @@ -259,7 +272,7 @@ impl InstanceWorker { .domain(self.instance.domain.clone()) .updated(Some(naive_now())) .build(); - Instance::update(pool, self.instance.id, form).await?; + Instance::update(&mut self.context.pool(), self.instance.id, form).await?; } } Ok(()) @@ -269,20 +282,17 @@ impl InstanceWorker { /// most often this will return 0 values (if instance doesn't care about the activity) /// or 1 value (the shared inbox) /// > 1 values only happens for non-lemmy software - async fn get_inbox_urls( - &mut self, - pool: &mut DbPool<'_>, - activity: &SentActivity, - ) -> Result> { + async fn get_inbox_urls(&mut self, activity: &SentActivity) -> Result> { let mut inbox_urls: HashSet = HashSet::new(); if activity.send_all_instances { if !self.site_loaded { - self.site = Site::read_from_instance_id(pool, self.instance.id).await?; + self.site = Site::read_from_instance_id(&mut self.context.pool(), self.instance.id).await?; self.site_loaded = true; } if let Some(site) = &self.site { - // Nutomic: Most non-lemmy software wont have a site row. That means it cant handle these activities. So handling it like this is fine. + // Nutomic: Most non-lemmy software wont have a site row. That means it cant handle these + // activities. So handling it like this is fine. inbox_urls.insert(site.inbox_url.inner().clone()); } } @@ -302,22 +312,18 @@ impl InstanceWorker { Ok(inbox_urls) } - async fn update_communities(&mut self, pool: &mut DbPool<'_>) -> Result<()> { + async fn update_communities(&mut self) -> Result<()> { if (Utc::now() - self.last_full_communities_fetch) > *FOLLOW_REMOVALS_RECHECK_DELAY { // process removals every hour (self.followed_communities, self.last_full_communities_fetch) = self - .get_communities(pool, self.instance.id, Utc.timestamp_nanos(0)) + .get_communities(self.instance.id, Utc.timestamp_nanos(0)) .await?; self.last_incremental_communities_fetch = self.last_full_communities_fetch; } if (Utc::now() - self.last_incremental_communities_fetch) > *FOLLOW_ADDITIONS_RECHECK_DELAY { // process additions every minute let (news, time) = self - .get_communities( - pool, - self.instance.id, - self.last_incremental_communities_fetch, - ) + .get_communities(self.instance.id, self.last_incremental_communities_fetch) .await?; self.followed_communities.extend(news); self.last_incremental_communities_fetch = time; @@ -325,32 +331,38 @@ impl InstanceWorker { Ok(()) } - /// get a list of local communities with the remote inboxes on the given instance that cares about them + /// get a list of local communities with the remote inboxes on the given instance that cares about + /// them async fn get_communities( &mut self, - pool: &mut DbPool<'_>, instance_id: InstanceId, last_fetch: DateTime, ) -> Result<(HashMap>, DateTime)> { let new_last_fetch = - Utc::now() - chrono::TimeDelta::try_seconds(10).expect("TimeDelta out of bounds"); // update to time before fetch to ensure overlap. subtract 10s to ensure overlap even if published date is not exact + Utc::now() - chrono::TimeDelta::try_seconds(10).expect("TimeDelta out of bounds"); // update to time before fetch to ensure overlap. subtract 10s to ensure overlap even if + // published date is not exact Ok(( - CommunityFollowerView::get_instance_followed_community_inboxes(pool, instance_id, last_fetch) - .await? - .into_iter() - .fold(HashMap::new(), |mut map, (c, u)| { - map.entry(c).or_default().insert(u.into()); - map - }), + CommunityFollowerView::get_instance_followed_community_inboxes( + &mut self.context.pool(), + instance_id, + last_fetch, + ) + .await? + .into_iter() + .fold(HashMap::new(), |mut map, (c, u)| { + map.entry(c).or_default().insert(u.into()); + map + }), new_last_fetch, )) } - async fn save_and_send_state(&mut self, pool: &mut DbPool<'_>) -> Result<()> { + async fn save_and_send_state(&mut self) -> Result<()> { self.last_state_insert = Utc::now(); - FederationQueueState::upsert(pool, &self.state).await?; - self - .stats_sender - .send((self.instance.domain.clone(), self.state.clone()))?; + FederationQueueState::upsert(&mut self.context.pool(), &self.state).await?; + self.stats_sender.send(FederationQueueStateWithDomain { + state: self.state.clone(), + domain: self.instance.domain.clone(), + })?; Ok(()) } } diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml index 1403f92e7..0d18e4f1f 100644 --- a/crates/routes/Cargo.toml +++ b/crates/routes/Cargo.toml @@ -34,4 +34,4 @@ once_cell = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } urlencoding = { workspace = true } -rss = "2.0.7" +rss = "2.0.8" diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index 5e3db357a..f08f28c4a 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -355,7 +355,7 @@ async fn get_feed_front( let posts = PostQuery { listing_type: (Some(ListingType::Subscribed)), - local_user: (Some(&local_user)), + local_user: (Some(&local_user.local_user)), sort: (Some(*sort_type)), limit: (Some(*limit)), page: (Some(*page)), diff --git a/crates/routes/src/nodeinfo.rs b/crates/routes/src/nodeinfo.rs index 665756864..bcb835309 100644 --- a/crates/routes/src/nodeinfo.rs +++ b/crates/routes/src/nodeinfo.rs @@ -9,15 +9,21 @@ use lemmy_utils::{ VERSION, }; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use url::Url; +/// A description of the nodeinfo endpoint is here: +/// https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md pub fn config(cfg: &mut web::ServiceConfig) { cfg .route( - "/nodeinfo/2.0.json", + "/nodeinfo/2.1", web::get().to(node_info).wrap(cache_1hour()), ) - .service(web::redirect("/version", "/nodeinfo/2.0.json")) + .service(web::redirect("/version", "/nodeinfo/2.1")) + // For backwards compatibility, can be removed after Lemmy 0.20 + .service(web::redirect("/nodeinfo/2.0.json", "/nodeinfo/2.1")) + .service(web::redirect("/nodeinfo/2.1.json", "/nodeinfo/2.1")) .route( "/.well-known/nodeinfo", web::get().to(node_info_well_known).wrap(cache_3days()), @@ -27,9 +33,9 @@ pub fn config(cfg: &mut web::ServiceConfig) { async fn node_info_well_known(context: web::Data) -> LemmyResult { let node_info = NodeInfoWellKnown { links: vec![NodeInfoWellKnownLinks { - rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, + rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.1")?, href: Url::parse(&format!( - "{}/nodeinfo/2.0.json", + "{}/nodeinfo/2.1", &context.settings().get_protocol_and_hostname(), ))?, }], @@ -69,19 +75,19 @@ async fn node_info(context: web::Data) -> Result, } #[derive(Serialize, Deserialize, Debug)] -struct NodeInfoWellKnownLinks { +pub struct NodeInfoWellKnownLinks { pub rel: Url, pub href: Url, } @@ -97,7 +103,7 @@ pub struct NodeInfo { pub open_registrations: Option, /// These fields are required by the spec for no reason pub services: Option, - pub metadata: Option>, + pub metadata: Option>, } #[derive(Serialize, Deserialize, Debug, Default)] diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 3834c3a47..046c0a872 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -75,7 +75,7 @@ rosetta-i18n = { workspace = true, optional = true } tokio = { workspace = true, optional = true } urlencoding = { workspace = true, optional = true } openssl = { version = "0.10.64", optional = true } -html2text = { version = "0.6.0", optional = true } +html2text = { version = "0.12.5", optional = true } deser-hjson = { version = "2.2.4", optional = true } smart-default = { version = "0.7.1", optional = true } lettre = { version = "0.11.7", features = [ diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 79d37cd08..324c08ccb 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -161,13 +161,13 @@ pub enum LemmyErrorType { PermissiveRegex, InvalidRegex, CaptchaIncorrect, - PasswordResetLimitReached, CouldntCreateAudioCaptcha, InvalidUrlScheme, CouldntSendWebmention, ContradictingFilters, InstanceBlockAlreadyExists, - /// Thrown when an API call is submitted with more than 1000 array elements, see [[MAX_API_PARAM_ELEMENTS]] + /// Thrown when an API call is submitted with more than 1000 array elements, see + /// [[MAX_API_PARAM_ELEMENTS]] TooManyItems, CommunityHasNoFollowers, BanExpirationInPast, diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 95c1d0144..1adb3f6cf 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -56,6 +56,7 @@ pub fn spawn_try_task( tracing::warn!("error in spawn: {e}"); } } - .in_current_span(), // this makes sure the inner tracing gets the same context as where spawn was called + .in_current_span(), /* this makes sure the inner tracing gets the same context as where + * spawn was called */ ); } diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index 3f89aee7e..5f1b6f7f5 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -46,9 +46,10 @@ impl Bucket { fn update(self, now: InstantSecs, config: BucketConfig) -> Self { let secs_since_last_checked = now.secs.saturating_sub(self.last_checked.secs); - // For `secs_since_last_checked` seconds, the amount of tokens increases by `capacity` every `secs_to_refill` seconds. - // The amount of tokens added per second is `capacity / secs_to_refill`. - // The expression below is like `secs_since_last_checked * (capacity / secs_to_refill)` but with precision and non-overflowing multiplication. + // For `secs_since_last_checked` seconds, the amount of tokens increases by `capacity` every + // `secs_to_refill` seconds. The amount of tokens added per second is `capacity / + // secs_to_refill`. The expression below is like `secs_since_last_checked * (capacity / + // secs_to_refill)` but with precision and non-overflowing multiplication. let added_tokens = u64::from(secs_since_last_checked) * u64::from(config.capacity) / u64::from(config.secs_to_refill); @@ -124,8 +125,9 @@ impl MapLevel for Map { ..config }); - // Remove groups that are no longer needed if the hash map's existing allocation has no space for new groups. - // This is done before calling `HashMap::entry` because that immediately allocates just like `HashMap::insert`. + // Remove groups that are no longer needed if the hash map's existing allocation has no space + // for new groups. This is done before calling `HashMap::entry` because that immediately + // allocates just like `HashMap::insert`. if (self.capacity() == self.len()) && !self.contains_key(&addr_part) { self.remove_full_buckets(now, configs); } @@ -219,7 +221,8 @@ impl RateLimitedGroup { if new_bucket.tokens == 0 { // Not enough tokens yet - // Setting `bucket` to `new_bucket` here is useless and would cause the bucket to start over at 0 tokens because of rounding + // Setting `bucket` to `new_bucket` here is useless and would cause the bucket to start over + // at 0 tokens because of rounding false } else { // Consume 1 token @@ -239,10 +242,12 @@ pub struct RateLimitState { /// /// The same thing happens for the first 48 and 56 bits, but with increased capacity. /// - /// This is done because all users can easily switch to any other IPv6 address that has the same first 64 bits. - /// It could be as low as 48 bits for some networks, which is the reason for 48 and 56 bit address groups. + /// This is done because all users can easily switch to any other IPv6 address that has the same + /// first 64 bits. It could be as low as 48 bits for some networks, which is the reason for 48 + /// and 56 bit address groups. ipv6_buckets: Map<[u8; 6], Map>>, - /// This stores a `BucketConfig` for each `ActionType`. `EnumMap` makes it impossible to have a missing `BucketConfig`. + /// This stores a `BucketConfig` for each `ActionType`. `EnumMap` makes it impossible to have a + /// missing `BucketConfig`. bucket_configs: EnumMap, } @@ -339,7 +344,8 @@ mod tests { let mut rate_limiter = RateLimitState::new(bucket_configs); let mut now = InstantSecs::now(); - // Do 1 `Message` and 1 `Post` action for each IP address, and expect the limit to not be reached + // Do 1 `Message` and 1 `Post` action for each IP address, and expect the limit to not be + // reached let ips = [ "123.123.123.123", "1:2:3::", diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 91a5b37f4..7fbe84e80 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -38,7 +38,8 @@ pub struct Settings { /// Whether the site is available over TLS. Needs to be true for federation to work. #[default(true)] pub tls_enabled: bool, - /// Set the URL for opentelemetry exports. If you do not have an opentelemetry collector, do not set this option + /// Set the URL for opentelemetry exports. If you do not have an opentelemetry collector, do not + /// set this option #[default(None)] #[doku(skip)] pub opentelemetry_url: Option, @@ -82,10 +83,12 @@ pub struct PictrsConfig { /// Backwards compatibility with 0.18.1. False is equivalent to `image_mode: None`, true is /// equivalent to `image_mode: StoreLinkPreviews`. /// + /// TODO /// To be removed in 0.20 pub(super) cache_external_link_previews: Option, - /// Specifies how to handle remote images, so that users don't have to connect directly to remote servers. + /// Specifies how to handle remote images, so that users don't have to connect directly to remote + /// servers. #[default(PictrsImageMode::StoreLinkPreviews)] pub(super) image_mode: PictrsImageMode, @@ -107,10 +110,11 @@ pub enum PictrsImageMode { /// This is the default behaviour, and also matches Lemmy 0.18. #[default] StoreLinkPreviews, - /// If enabled, all images from remote domains are rewritten to pass through `/api/v3/image_proxy`, - /// including embedded images in markdown. Images are stored temporarily in pict-rs for caching. - /// This improves privacy as users don't expose their IP to untrusted servers, and decreases load - /// on other servers. However it increases bandwidth use for the local server. + /// If enabled, all images from remote domains are rewritten to pass through + /// `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily + /// in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted + /// servers, and decreases load on other servers. However it increases bandwidth use for the + /// local server. /// /// Requires pict-rs 0.5 ProxyAllImages, diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index c7c8a88e3..ba94372fa 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -101,7 +101,7 @@ mod test { // These helped with testing // #[test] // fn test_send_email() { - // let result = send_email("not a subject", "test_email@gmail.com", "ur user", "

HI there

"); - // assert!(result.is_ok()); + // let result = send_email("not a subject", "test_email@gmail.com", "ur user", "

HI + // there

"); assert!(result.is_ok()); // } } diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index a6539f1b1..8891411a5 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -11,15 +11,17 @@ static VALID_MATRIX_ID_REGEX: Lazy = Lazy::new(|| { }); // taken from https://en.wikipedia.org/wiki/UTM_parameters static CLEAN_URL_PARAMS_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"^utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid$") - .expect("compile regex") + Regex::new( + r"^(utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid)=", + ) + .expect("compile regex") }); const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"]; const BODY_MAX_LENGTH: usize = 10000; const POST_BODY_MAX_LENGTH: usize = 50000; const BIO_MAX_LENGTH: usize = 300; -const ALT_TEXT_MAX_LENGTH: usize = 300; +const ALT_TEXT_MAX_LENGTH: usize = 1500; const SITE_NAME_MAX_LENGTH: usize = 20; const SITE_NAME_MIN_LENGTH: usize = 1; const SITE_DESCRIPTION_MAX_LENGTH: usize = 150; @@ -158,14 +160,12 @@ pub fn is_valid_post_title(title: &str) -> LemmyResult<()> { } /// This could be post bodies, comments, or any description field -pub fn is_valid_body_field(body: &Option, post: bool) -> LemmyResult<()> { - if let Some(body) = body { - if post { - max_length_check(body, POST_BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?; - } else { - max_length_check(body, BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?; - }; - } +pub fn is_valid_body_field(body: &str, post: bool) -> LemmyResult<()> { + if post { + max_length_check(body, POST_BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?; + } else { + max_length_check(body, BODY_MAX_LENGTH, LemmyErrorType::InvalidBodyField)?; + }; Ok(()) } @@ -173,16 +173,14 @@ pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> { max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow) } -pub fn is_valid_alt_text_field(alt_text: &Option) -> LemmyResult<()> { - if let Some(alt_text) = alt_text { - max_length_check( - alt_text, - ALT_TEXT_MAX_LENGTH, - LemmyErrorType::AltTextLengthOverflow, - ) - } else { - Ok(()) - } +pub fn is_valid_alt_text_field(alt_text: &str) -> LemmyResult<()> { + max_length_check( + alt_text, + ALT_TEXT_MAX_LENGTH, + LemmyErrorType::AltTextLengthOverflow, + )?; + + Ok(()) } /// Checks the site name length, the limit as defined in the DB. @@ -260,12 +258,11 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult