diff --git a/.woodpecker.yml b/.woodpecker.yml index 078561be6..cb6580e32 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -2,7 +2,7 @@ # See https://github.com/woodpecker-ci/woodpecker/issues/1677 variables: - - &rust_image "rust:1.80" + - &rust_image "rust:1.81" - &rust_nightly_image "rustlang/rust:nightly" - &install_pnpm "corepack enable pnpm" - &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin" @@ -42,14 +42,14 @@ steps: - event: [pull_request, tag] prettier_check: - image: tmknom/prettier:3.0.0 + image: tmknom/prettier:3.2.5 commands: - prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml' when: - event: pull_request toml_fmt: - image: tamasfe/taplo:0.8.1 + image: tamasfe/taplo:0.9.3 commands: - taplo format --check when: diff --git a/Cargo.lock b/Cargo.lock index 1658ff0e3..3cfec53eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772" [[package]] name = "activitypub_federation" -version = "0.6.0-alpha1" +version = "0.6.0-alpha2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a91a6511b03a39b5c3d9b0351b1899e9a7774c44506ae648bea91e6f0b4bdd8" +checksum = "4877d467ddf2fac85e9ee33aba6f2560df14125b8bfa864f85ab40e9b87753a9" dependencies = [ "activitystreams-kinds", "actix-web", @@ -42,7 +42,7 @@ dependencies = [ "pin-project-lite", "rand", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "rsa", "serde", @@ -70,7 +70,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-sink", @@ -96,27 +96,11 @@ dependencies = [ "smallvec", ] -[[package]] -name = "actix-form-data" -version = "0.7.0-beta.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2355a841a5d9a6c616d6a4f31336064116d206e6c1830de22730f983613a05" -dependencies = [ - "actix-multipart", - "actix-web", - "futures-core", - "mime", - "streem", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "actix-http" -version = "3.7.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" dependencies = [ "actix-codec", "actix-rt", @@ -125,7 +109,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "brotli", "bytes", "bytestring", @@ -133,7 +117,7 @@ dependencies = [ "encoding_rs", "flate2", "futures-core", - "h2 0.3.26", + "h2", "http 0.2.12", "httparse", "httpdate", @@ -159,30 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.66", -] - -[[package]] -name = "actix-multipart" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b960e2aea75f49c8f069108063d12a48d329fc8b60b786dfc7552a9d5918d2d" -dependencies = [ - "actix-utils", - "actix-web", - "bytes", - "derive_more", - "futures-core", - "futures-util", - "httparse", - "local-waker", - "log", - "memchr", - "mime", - "serde", - "serde_json", - "serde_plain", - "tokio", + "syn 2.0.77", ] [[package]] @@ -201,9 +162,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ "futures-core", "tokio", @@ -211,16 +172,16 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" dependencies = [ "actix-rt", "actix-service", "actix-utils", "futures-core", "futures-util", - "mio 0.8.11", + "mio", "socket2", "tokio", "tracing", @@ -251,7 +212,7 @@ dependencies = [ "pin-project-lite", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "tracing", ] @@ -317,7 +278,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -337,9 +298,9 @@ dependencies = [ [[package]] name = "actix-web-prom" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76743e67d4e7efa9fc2ac7123de0dd7b2ca592668e19334f1d81a3b077afc6ac" +checksum = "56a34f1825c3ae06567a9d632466809bbf34963c86002e8921b64f32d48d289d" dependencies = [ "actix-web", "futures-core", @@ -352,9 +313,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] @@ -365,6 +326,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -425,9 +392,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -446,27 +413,27 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -474,9 +441,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" dependencies = [ "backtrace", ] @@ -499,9 +466,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ "flate2", "futures-core", @@ -521,58 +488,30 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "atom_syndication" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f34613907f31c9dbef0240156db3c9263f34842b6e1a8999d2304ea62c8a30" +checksum = "2a3a5ed3201df5658d1aa45060c5a57dc9dba8a8ada20d696d67cb0c479ee043" dependencies = [ "chrono", "derive_builder", "diligent-date-parser", "never", - "quick-xml 0.31.0", + "quick-xml 0.36.1", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.3.0" @@ -588,7 +527,6 @@ dependencies = [ "aws-lc-sys", "mirai-annotations", "paste", - "untrusted 0.7.1", "zeroize", ] @@ -607,72 +545,21 @@ dependencies = [ "paste", ] -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.8.0", "object", "rustc-demangle", + "windows-targets 0.52.6", ] -[[package]] -name = "barrel" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9e605929a6964efbec5ac0884bd0fe93f12a3b1eb271f52c251316640c68d9" - [[package]] name = "base32" version = "0.4.0" @@ -703,19 +590,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bb8" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7c2093d15d6a1d33b1f972e1c5ea3177748742b97a5f392aa83a65262c6780" -dependencies = [ - "async-trait", - "futures-channel", - "futures-util", - "parking_lot 0.12.3", - "tokio", -] - [[package]] name = "bcder" version = "0.7.4" @@ -754,7 +628,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -767,7 +641,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.66", + "syn 2.0.77", "which", ] @@ -794,12 +668,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -dependencies = [ - "serde", -] +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -820,12 +691,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "blurhash-update" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb5ea45aeb912f2dd334834e64ecf674a6673d57c73e9d12de0298b9bf98ee8" - [[package]] name = "brotli" version = "6.0.0" @@ -855,9 +720,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -867,9 +732,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "bytestring" @@ -896,9 +761,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" dependencies = [ "jobserver", "libc", @@ -974,9 +839,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -984,9 +849,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -996,21 +861,35 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "clearurls" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e291c00af89ac0a5b400d9ba46a682e38015ae3cd8926dbbe85b3b864d550be3" +dependencies = [ + "linkify", + "percent-encoding", + "regex", + "serde", + "serde_json", + "url", +] [[package]] name = "clokwerk" @@ -1023,40 +902,13 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.50" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" dependencies = [ "cc", ] -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -1065,9 +917,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -1088,59 +940,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "config" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" -dependencies = [ - "lazy_static", - "nom", - "pathdiff", - "ron", - "serde", - "serde_json", - "toml 0.8.14", - "yaml-rust", -] - -[[package]] -name = "console-api" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" -dependencies = [ - "futures-core", - "prost", - "prost-types", - "tonic 0.10.2", - "tracing-core", -] - -[[package]] -name = "console-subscriber" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" -dependencies = [ - "console-api", - "crossbeam-channel", - "crossbeam-utils", - "futures-task", - "hdrhistogram", - "humantime", - "prost-types", - "serde", - "serde_json", - "thread_local", - "tokio", - "tokio-stream", - "tonic 0.10.2", - "tracing", - "tracing-core", - "tracing-subscriber", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1149,18 +948,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -1202,15 +1001,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -1270,12 +1069,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.9", - "darling_macro 0.20.9", + "darling_core 0.20.10", + "darling_macro 0.20.10", ] [[package]] @@ -1294,16 +1093,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1319,26 +1118,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.9", + "darling_core 0.20.10", "quote", - "syn 2.0.66", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.10", + "syn 2.0.77", ] [[package]] @@ -1381,23 +1167,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -1427,51 +1200,51 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "derive_builder" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" dependencies = [ - "darling 0.20.9", + "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "derive_builder_macro" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -1489,14 +1262,13 @@ version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "chrono", "diesel_derives", "itoa", "pq-sys", "serde_json", - "time", "uuid", ] @@ -1507,7 +1279,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acada1517534c92d3f382217b485db8a8638f111b0e3f2a2a8e26165050f77be" dependencies = [ "async-trait", - "bb8", "deadpool 0.9.5", "diesel", "futures-util", @@ -1525,7 +1296,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1536,7 +1307,7 @@ checksum = "d5adf688c584fe33726ce0e2898f608a2a92578ac94a4a92fcecf73214fe0716" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1548,7 +1319,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1578,7 +1349,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1616,7 +1387,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1657,9 +1428,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" @@ -1669,9 +1440,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elementtree" @@ -1694,9 +1465,9 @@ dependencies = [ [[package]] name = "email_address" -version = "0.2.4" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encoding_rs" @@ -1730,7 +1501,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1794,31 +1565,12 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible_collections" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" -dependencies = [ - "hashbrown 0.13.2", -] - [[package]] name = "fancy-regex" version = "0.11.0" @@ -1831,9 +1583,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" @@ -1844,20 +1596,14 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "flagset" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" - [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1866,21 +1612,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1896,16 +1627,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -1924,9 +1645,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1939,9 +1660,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1949,15 +1670,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1966,38 +1687,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -2011,15 +1732,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2045,9 +1757,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "glob" @@ -2067,26 +1779,7 @@ dependencies = [ "futures-sink", "futures-util", "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", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -2099,15 +1792,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -2118,19 +1802,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "hdrhistogram" -version = "7.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" -dependencies = [ - "base64 0.21.7", - "byteorder", - "flate2", - "nom", - "num-traits", -] - [[package]] name = "heck" version = "0.4.1" @@ -2204,9 +1875,9 @@ dependencies = [ [[package]] name = "html2text" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c66ee488a63a92237d5b48875b7e05bb293be8fb2894641c8118b60c08ab5ef" +checksum = "042a9677c258ac2952dd026bb0cd21972f00f644a5a38f5a215cb22cdaf6834e" dependencies = [ "html5ever 0.27.0", "markup5ever 0.12.1", @@ -2240,7 +1911,7 @@ dependencies = [ "markup5ever 0.12.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -2278,9 +1949,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -2288,14 +1959,14 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -2318,7 +1989,7 @@ dependencies = [ "base64 0.22.1", "http-signature-normalization", "httpdate", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "sha2", "thiserror", @@ -2327,9 +1998,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -2337,23 +2008,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2376,11 +2041,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2388,6 +2051,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.3" @@ -2398,50 +2075,25 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", - "rustls", + "rustls 0.23.14", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.29", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.29", - "native-tls", - "tokio", - "tokio-native-tls", + "webpki-roots 0.26.5", ] [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "hyper 1.4.1", "pin-project-lite", "socket2", @@ -2468,14 +2120,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8215279f83f9b829403812f845aa2d0dd5966332aa2fd0334a375256f3dd0322" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2609,7 +2261,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -2669,12 +2321,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "1.9.3" @@ -2688,9 +2334,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -2706,26 +2352,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -2773,18 +2410,18 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -2812,11 +2449,11 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -2877,7 +2514,7 @@ dependencies = [ "moka", "pretty_assertions", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "rosetta-i18n", "serde", @@ -2901,6 +2538,7 @@ dependencies = [ "actix-web", "anyhow", "bcrypt", + "chrono", "futures", "lemmy_api_common", "lemmy_db_schema", @@ -2908,6 +2546,9 @@ dependencies = [ "lemmy_db_views_actor", "lemmy_utils", "moka", + "serde", + "serde_json", + "serde_with", "tracing", "url", "uuid", @@ -2938,7 +2579,7 @@ dependencies = [ "lemmy_utils", "moka", "pretty_assertions", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "serde_with", @@ -2989,7 +2630,7 @@ dependencies = [ "moka", "pretty_assertions", "regex", - "rustls", + "rustls 0.23.14", "serde", "serde_json", "serde_with", @@ -3000,7 +2641,6 @@ dependencies = [ "tokio-postgres-rustls", "tracing", "ts-rs", - "typed-builder", "url", "uuid", ] @@ -3078,7 +2718,7 @@ dependencies = [ "lemmy_utils", "mockall", "moka", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde_json", "serial_test", "test-context", @@ -3105,7 +2745,7 @@ dependencies = [ "lemmy_db_views", "lemmy_db_views_actor", "lemmy_utils", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "rss", "serde", @@ -3136,13 +2776,12 @@ dependencies = [ "lemmy_federate", "lemmy_routes", "lemmy_utils", - "pict-rs", "pretty_assertions", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "reqwest-tracing", - "rustls", + "rustls 0.23.14", "serde_json", "serial_test", "tokio", @@ -3159,6 +2798,7 @@ dependencies = [ "actix-web", "anyhow", "cfg-if", + "clearurls", "deser-hjson", "diesel", "doku", @@ -3169,9 +2809,13 @@ dependencies = [ "itertools 0.13.0", "lettre", "markdown-it", + "markdown-it-block-spoiler", + "markdown-it-ruby", + "markdown-it-sub", + "markdown-it-sup", "pretty_assertions", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "rosetta-build", "rosetta-i18n", @@ -3189,9 +2833,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1658ac89b01055e3ba23d66ecf6f8b97388ebe34ce125e49f8ef71234fb8aaba" +checksum = "69f204773bab09b150320ea1c83db41dc6ee606a4bc36dc1f43005fe7b58ce06" dependencies = [ "async-trait", "base64 0.22.1", @@ -3207,27 +2851,27 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", - "rustls", - "rustls-pemfile 2.1.2", + "rustls 0.23.14", + "rustls-pemfile 2.1.3", "rustls-pki-types", "socket2", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "url", - "webpki-roots", + "webpki-roots 0.26.5", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -3239,12 +2883,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3301,12 +2939,11 @@ dependencies = [ [[package]] name = "lodepng" -version = "3.10.1" +version = "3.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42d298694b14401847de29abd44adf278b42e989e516deac7b72018400002d8" +checksum = "7b2dea7cda68e381418c985fd8f32a9c279a21ae8c715f2376adb20c27a0fad3" dependencies = [ "crc32fast", - "fallible_collections", "flate2", "libc", "rgb", @@ -3314,9 +2951,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "mac" @@ -3347,6 +2984,44 @@ dependencies = [ "unicode-general-category", ] +[[package]] +name = "markdown-it-block-spoiler" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008a8e4184fd08b5dca0f2b5b2ef8f126c1e83ca797c44ee41f8d7765951360c" +dependencies = [ + "itertools 0.13.0", + "markdown-it", +] + +[[package]] +name = "markdown-it-ruby" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3505f4ada7c372e7f5eb4b07850bf5921193bc0bd43cb18991233999c9134d4" +dependencies = [ + "itertools 0.13.0", + "markdown-it", +] + +[[package]] +name = "markdown-it-sub" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8abe3aa8927af2314644b3aae37393241a229e869ff9c95ac640749e08357d2a" +dependencies = [ + "markdown-it", +] + +[[package]] +name = "markdown-it-sup" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ae949e78c7a615f88a47019d51b65962bfc5c4cbc65fa81eae8b9b2506d1cb1" +dependencies = [ + "markdown-it", +] + [[package]] name = "markup5ever" version = "0.11.0" @@ -3410,15 +3085,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" 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" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" @@ -3443,54 +3112,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "metrics" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" -dependencies = [ - "ahash", - "portable-atomic", -] - -[[package]] -name = "metrics-exporter-prometheus" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26eb45aff37b45cff885538e1dcbd6c2b462c04fe84ce0155ea469f325672c98" -dependencies = [ - "base64 0.22.1", - "http-body-util", - "hyper 1.4.1", - "hyper-util", - "indexmap 2.2.6", - "ipnet", - "metrics", - "metrics-util", - "quanta", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "metrics-util" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.14.5", - "metrics", - "num_cpus", - "quanta", - "sketches-ddsketch", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "migrations_internals" @@ -3499,7 +3123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" dependencies = [ "serde", - "toml 0.7.8", + "toml", ] [[package]] @@ -3527,24 +3151,21 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", ] [[package]] -name = "mio" -version = "0.8.11" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", + "adler2", ] [[package]] @@ -3555,6 +3176,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -3588,7 +3210,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -3605,7 +3227,7 @@ dependencies = [ "event-listener", "futures-util", "once_cell", - "parking_lot 0.12.3", + "parking_lot", "quanta", "rustc_version", "smallvec", @@ -3621,29 +3243,6 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "never" version = "0.1.0" @@ -3678,9 +3277,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -3751,9 +3350,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -3764,153 +3363,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.5.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "opentelemetry" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" -dependencies = [ - "async-trait", - "futures-core", - "http 0.2.12", - "opentelemetry", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "thiserror", - "tokio", - "tonic 0.11.0", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", - "tonic 0.11.0", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" -dependencies = [ - "async-trait", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "lazy_static", - "once_cell", - "opentelemetry", - "ordered-float", - "percent-encoding", - "rand", - "thiserror", - "tokio", - "tokio-stream", -] - -[[package]] -name = "ordered-float" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" -dependencies = [ - "num-traits", -] - [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -3919,21 +3382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -3944,7 +3393,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -3955,12 +3404,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "pem" version = "3.0.4" @@ -4050,7 +3493,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -4059,72 +3502,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "pict-rs" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bbee61836cce10f7cf733196b7c0701e7ea6d0b617da68a3e6e4311b6262c2b" -dependencies = [ - "actix-form-data", - "actix-web", - "async-trait", - "barrel", - "base64 0.22.1", - "bb8", - "blurhash-update", - "clap", - "color-eyre", - "config", - "console-subscriber", - "dashmap", - "diesel", - "diesel-async", - "diesel-derive-enum", - "futures-core", - "hex", - "md-5", - "metrics", - "metrics-exporter-prometheus", - "mime", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry_sdk", - "pin-project-lite", - "refinery", - "reqwest 0.12.7", - "reqwest-middleware", - "reqwest-tracing", - "rustls", - "rustls-channel-resolver", - "rustls-pemfile 2.1.2", - "rusty-s3", - "serde", - "serde-tuple-vec-map", - "serde_json", - "serde_urlencoded", - "sha2", - "sled", - "streem", - "subtle", - "thiserror", - "time", - "tokio", - "tokio-postgres", - "tokio-postgres-generic-rustls", - "tokio-util", - "toml 0.8.14", - "tracing", - "tracing-actix-web", - "tracing-error", - "tracing-log", - "tracing-opentelemetry", - "tracing-subscriber", - "url", - "uuid", - "webpki-roots", + "siphasher", ] [[package]] @@ -4144,7 +3522,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -4188,14 +3566,13 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", - "indexmap 2.2.6", - "line-wrap", - "quick-xml 0.31.0", + "base64 0.22.1", + "indexmap 2.5.0", + "quick-xml 0.32.0", "serde", "time", ] @@ -4210,27 +3587,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide", -] - -[[package]] -name = "portable-atomic" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" - -[[package]] -name = "postgres" -version = "0.19.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7915b33ed60abc46040cbcaa25ffa1c7ec240668e0477c4f3070786f5916d451" -dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", - "log", - "tokio", - "tokio-postgres", + "miniz_oxide 0.7.4", ] [[package]] @@ -4253,17 +3610,13 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9" +checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f" dependencies = [ "bytes", "fallible-iterator", "postgres-protocol", - "serde", - "serde_json", - "time", - "uuid", ] [[package]] @@ -4274,9 +3627,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pq-sys" @@ -4295,9 +3651,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "predicates-core", @@ -4305,15 +3661,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -4321,9 +3677,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -4331,19 +3687,19 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -4354,7 +3710,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "hex", "lazy_static", "procfs-core", @@ -4367,7 +3723,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "hex", ] @@ -4382,44 +3738,12 @@ dependencies = [ "lazy_static", "libc", "memchr", - "parking_lot 0.12.3", + "parking_lot", "procfs", "protobuf", "thiserror", ] -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - [[package]] name = "protobuf" version = "2.28.0" @@ -4428,9 +3752,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psm" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ "cc", ] @@ -4452,22 +3776,11 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", - "serde", -] - -[[package]] -name = "quick-xml" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" -dependencies = [ - "encoding_rs", - "memchr", ] [[package]] @@ -4491,7 +3804,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls", + "rustls 0.23.14", "socket2", "thiserror", "tokio", @@ -4508,7 +3821,7 @@ dependencies = [ "rand", "ring", "rustc-hash 2.0.0", - "rustls", + "rustls 0.23.14", "slab", "thiserror", "tinyvec", @@ -4517,31 +3830,31 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "rand" @@ -4575,11 +3888,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.0.2" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -4590,92 +3903,28 @@ checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -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", -] - -[[package]] -name = "refinery-core" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf253999e1899ae476c910b994959e341d84c4389ba9533d3dacbe06df04825" -dependencies = [ - "async-trait", - "cfg-if", - "log", - "postgres", - "regex", - "serde", - "siphasher 1.0.1", - "thiserror", - "time", - "tokio", - "tokio-postgres", - "toml 0.8.14", - "url", - "walkdir", -] - -[[package]] -name = "refinery-macros" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd81f69687fe8a1fa10995108b3ffc7cdbd63e682a4f8fbfd1020130780d7e17" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "refinery-core", - "regex", - "syn 2.0.66", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -4689,20 +3938,20 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", ] [[package]] name = "regex-lite" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" @@ -4712,9 +3961,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -4727,19 +3976,19 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", + "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", - "hyper-tls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.12", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4747,20 +3996,21 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.4", "winreg", ] [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "async-compression", "base64 0.22.1", @@ -4769,10 +4019,10 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "hyper 1.4.1", - "hyper-rustls", + "hyper-rustls 0.27.3", "hyper-util", "ipnet", "js-sys", @@ -4782,15 +4032,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-pemfile 2.1.2", + "rustls 0.23.14", + "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "tower-service", "url", @@ -4798,7 +4048,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.26.5", "windows-registry", ] @@ -4811,7 +4061,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.1.0", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "thiserror", "tower-service", @@ -4827,8 +4077,8 @@ dependencies = [ "async-trait", "getrandom", "http 1.1.0", - "matchit 0.8.2", - "reqwest 0.12.7", + "matchit", + "reqwest 0.12.8", "reqwest-middleware", "tracing", ] @@ -4841,9 +4091,9 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" [[package]] name = "rgb" -version = "0.8.37" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -4858,23 +4108,11 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.5.0", - "serde", - "serde_derive", -] - [[package]] name = "rosetta-build" version = "0.1.3" @@ -4947,20 +4185,20 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -4969,30 +4207,32 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] -[[package]] -name = "rustls-channel-resolver" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fede2a247359da6b4998f7723ec6468c2d6a577a5d8c17e54f21806426ad2290" -dependencies = [ - "nanorand", - "rustls", -] - [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -5004,9 +4244,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -5014,9 +4254,19 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "rustls-webpki" @@ -5027,7 +4277,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -5036,25 +4286,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "rusty-s3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa883f1b986a5249641e574ca0e11ac4fb9970b009c6fbb96fedaf4fa78db8" -dependencies = [ - "base64 0.21.7", - "hmac", - "md-5", - "percent-encoding", - "quick-xml 0.30.0", - "serde", - "serde_json", - "sha2", - "time", - "url", - "zeroize", -] - [[package]] name = "ryu" version = "1.0.18" @@ -5072,22 +4303,13 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.1" +version = "2.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc" +checksum = "0c947adb109a8afce5fc9c7bf951f87f146e9147b3a6a58413105628fb1d1e66" dependencies = [ "sdd", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scoped-futures" version = "0.1.3" @@ -5104,34 +4326,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sdd" -version = "0.2.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags 2.5.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" [[package]] name = "select" @@ -5152,31 +4361,22 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-tuple-vec-map" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04d0ebe0de77d7d445bb729a895dcb0a288854b267ca85f030ce51cdc578c82" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5185,27 +4385,18 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "itoa", "memchr", "ryu", "serde", ] -[[package]] -name = "serde_plain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -5224,15 +4415,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -5242,14 +4433,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ - "darling 0.20.9", + "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5261,7 +4452,7 @@ dependencies = [ "futures", "log", "once_cell", - "parking_lot 0.12.3", + "parking_lot", "scc", "serial_test_derive", ] @@ -5274,7 +4465,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5357,12 +4548,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "sitemap-rs" version = "0.2.1" @@ -5373,12 +4558,6 @@ dependencies = [ "xml-builder", ] -[[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" - [[package]] name = "slab" version = "0.4.9" @@ -5388,22 +4567,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -5418,7 +4581,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5431,12 +4594,6 @@ dependencies = [ "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" @@ -5461,25 +4618,15 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", -] - -[[package]] -name = "streem" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8b0c8184b0fe05b37dd75d66205195cd57563c6c87cb92134a025a34a6ab34" -dependencies = [ - "futures-core", - "pin-project-lite", + "windows-sys 0.59.0", ] [[package]] @@ -5496,7 +4643,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.3", + "parking_lot", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -5562,14 +4709,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -5584,9 +4731,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -5616,7 +4763,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5632,7 +4779,7 @@ dependencies = [ "fnv", "once_cell", "plist", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", "serde", "serde_derive", "serde_json", @@ -5668,18 +4815,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "tendril" version = "0.4.3" @@ -5724,27 +4859,27 @@ checksum = "78ea17a2dc368aeca6f554343ced1b1e31f76d63683fa8016e5844bd7a5144a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -5806,9 +4941,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -5819,27 +4954,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tls_codec" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.40.0" @@ -5849,26 +4963,15 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", - "parking_lot 0.12.3", + "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "tracing", "windows-sys 0.52.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.4.0" @@ -5877,24 +4980,14 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", + "syn 2.0.77", ] [[package]] name = "tokio-postgres" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3" +checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb" dependencies = [ "async-trait", "byteorder", @@ -5903,7 +4996,7 @@ dependencies = [ "futures-channel", "futures-util", "log", - "parking_lot 0.12.3", + "parking_lot", "percent-encoding", "phf 0.11.2", "pin-project-lite", @@ -5916,20 +5009,6 @@ dependencies = [ "whoami", ] -[[package]] -name = "tokio-postgres-generic-rustls" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e98c31c29b2666fb28720739e11476166be4ead1610a37dcd7414bb124413a" -dependencies = [ - "aws-lc-rs", - "rustls", - "tokio", - "tokio-postgres", - "tokio-rustls", - "x509-cert", -] - [[package]] name = "tokio-postgres-rustls" version = "0.12.0" @@ -5937,35 +5016,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" dependencies = [ "ring", - "rustls", + "rustls 0.23.14", "tokio", "tokio-postgres", - "tokio-rustls", + "tokio-rustls 0.26.0", "x509-certificate", ] +[[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.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.14", "rustls-pki-types", "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.12" @@ -5988,26 +5066,14 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.14", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -6018,78 +5084,11 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.13", -] - -[[package]] -name = "tonic" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.21.7", - "bytes", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.21.7", - "bytes", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", + "winnow", ] [[package]] @@ -6116,29 +5115,24 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", - "slab", "tokio", - "tokio-util", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -6154,16 +5148,14 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee9e39a66d9b615644893ffc1704d2a89b5b315b7fd0228ad3182ca9a306b19" +checksum = "284586dc201db407be8c9d721abad1b3a6dacbbce5cccecd4fd15a37db95ab0d" dependencies = [ "actix-web", "mutually_exclusive_features", - "opentelemetry", "pin-project", "tracing", - "tracing-opentelemetry", "uuid", ] @@ -6175,7 +5167,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -6188,16 +5180,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -6209,24 +5191,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry", - "opentelemetry_sdk", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - [[package]] name = "tracing-serde" version = "0.1.3" @@ -6276,7 +5240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -6311,30 +5275,10 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "termcolor", ] -[[package]] -name = "typed-builder" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06fbd5b8de54c5f7c91f6fe4cebb949be2125d7758e630bb58b1d831dbce600" -dependencies = [ - "typed-builder-macro", -] - -[[package]] -name = "typed-builder-macro" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9534daa9fd3ed0bd911d462a37f172228077e7abf18c18a5f67199d959205f8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "typenum" version = "1.17.0" @@ -6355,9 +5299,9 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -6370,9 +5314,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-width" @@ -6382,15 +5326,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "untrusted" @@ -6442,9 +5380,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -6470,9 +5408,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -6507,34 +5445,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -6544,9 +5483,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6554,22 +5493,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" @@ -6586,19 +5525,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -6606,9 +5535,9 @@ dependencies = [ [[package]] name = "webmention" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d07b90492f7b6fe35f5298fcd01c663d3c453e8c302dc86c7292c6681b8117d" +checksum = "c2c1a8d1f70dd7b5b5e2bf5fca4dd97fa5ed4e8adcf0b0ee4c6ebe1ebac7a2fe" dependencies = [ "anyhow", "nom", @@ -6634,9 +5563,15 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ "rustls-pki-types", ] @@ -6655,11 +5590,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", "web-sys", ] @@ -6682,11 +5617,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6752,6 +5687,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6882,15 +5826,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.50.0" @@ -6913,18 +5848,6 @@ 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" @@ -6946,9 +5869,9 @@ dependencies = [ [[package]] name = "xml-builder" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc4f1a86af7800dfc4056c7833648ea4515ae21502060b5c98114d828f5333b" +checksum = "5ef5f40cd674b9d9814545203f175ac29ffdcb6e006727f4d95797d7badd72e2" [[package]] name = "xml5ever" @@ -6983,9 +5906,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" @@ -7007,28 +5930,29 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -7048,7 +5972,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "synstructure", ] @@ -7069,7 +5993,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -7091,32 +6015,32 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "zstd" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 272691bc7..fc22d0d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ opt-level = "z" # Optimize for size. debug = 0 [features] -embed-pictrs = ["pict-rs"] json-log = ["tracing-subscriber/json"] default = [] @@ -91,7 +90,7 @@ lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" } lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" } lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" } lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" } -activitypub_federation = { version = "0.6.0-alpha1", default-features = false, features = [ +activitypub_federation = { version = "0.6.0-alpha2", default-features = false, features = [ "actix-web", ] } diesel = "2.1.6" @@ -132,7 +131,6 @@ 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.19.1" serial_test = "3.1.1" tokio = { version = "1.39.2", features = ["full"] } regex = "1.10.5" @@ -182,7 +180,6 @@ reqwest-middleware = { workspace = true } reqwest-tracing = { workspace = true } clokwerk = { workspace = true } serde_json = { workspace = true } -pict-rs = { version = "0.5.16", optional = true } rustls = { workspace = true } tokio.workspace = true actix-cors = "0.7.0" @@ -191,7 +188,7 @@ chrono = { workspace = true } prometheus = { version = "0.13.4", features = ["process"] } serial_test = { workspace = true } clap = { workspace = true } -actix-web-prom = "0.8.0" +actix-web-prom = "0.9.0" [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/api_tests/package.json b/api_tests/package.json index bc2d3ec2d..63c212d01 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/LemmyNet/lemmy", "author": "Dessalines", "license": "AGPL-3.0", - "packageManager": "pnpm@9.9.0", + "packageManager": "pnpm@9.12.0", "scripts": { "lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'", "fix": "prettier --write src && eslint --fix src", @@ -21,16 +21,16 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^22.0.2", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^9.8.0", + "@types/node": "^22.3.0", + "@typescript-eslint/eslint-plugin": "^8.1.0", + "@typescript-eslint/parser": "^8.1.0", + "eslint": "^9.9.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", - "lemmy-js-client": "0.19.5-alpha.1", + "lemmy-js-client": "0.20.0-alpha.11", "prettier": "^3.2.5", "ts-jest": "^29.1.0", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.0" + "typescript-eslint": "^8.1.0" } } diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 3b1970681..6807f43f8 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -10,40 +10,40 @@ importers: devDependencies: '@types/jest': specifier: ^29.5.12 - version: 29.5.12 + version: 29.5.13 '@types/node': - specifier: ^22.0.2 - version: 22.5.1 + specifier: ^22.3.0 + version: 22.7.4 '@typescript-eslint/eslint-plugin': - specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.1.0 + version: 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: ^8.0.0 - version: 8.0.0(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.1.0 + version: 8.8.1(eslint@9.12.0)(typescript@5.6.2) eslint: - specifier: ^9.8.0 - version: 9.9.1 + specifier: ^9.9.0 + version: 9.12.0 eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.2.1(eslint@9.9.1)(prettier@3.3.3) + version: 5.2.1(eslint@9.12.0)(prettier@3.3.3) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@22.5.1) + version: 29.7.0(@types/node@22.7.4) lemmy-js-client: - specifier: 0.19.5-alpha.1 - version: 0.19.5-alpha.1 + specifier: 0.20.0-alpha.11 + version: 0.20.0-alpha.11 prettier: specifier: ^3.2.5 version: 3.3.3 ts-jest: specifier: ^29.1.0 - version: 29.2.5(@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@22.5.1))(typescript@5.5.4) + version: 29.2.5(@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@22.7.4))(typescript@5.6.2) typescript: specifier: ^5.5.4 - version: 5.5.4 + version: 5.6.2 typescript-eslint: - specifier: ^8.0.0 - version: 8.0.0(eslint@9.9.1)(typescript@5.5.4) + specifier: ^8.1.0 + version: 8.8.1(eslint@9.12.0)(typescript@5.6.2) packages: @@ -51,8 +51,8 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.23.5': - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} '@babel/compat-data@7.23.5': @@ -113,6 +113,10 @@ packages: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} @@ -121,8 +125,8 @@ packages: resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.23.4': - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} '@babel/parser@7.23.9': @@ -224,32 +228,48 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/regexpp@4.11.1': + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.18.0': resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.6.0': + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.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.9.1': - resolution: {integrity: sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==} + '@eslint/js@9.12.0': + resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} 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} + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.0': + resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.5': + resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.0': - resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} '@istanbuljs/load-nyc-config@1.1.0': @@ -381,6 +401,9 @@ packages: '@types/babel__traverse@7.20.5': resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -393,11 +416,14 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/jest@29.5.12': - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@types/jest@29.5.13': + resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} - '@types/node@22.5.1': - resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.7.4': + resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -408,8 +434,8 @@ packages: '@types/yargs@17.0.32': resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} - '@typescript-eslint/eslint-plugin@8.0.0': - resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + '@typescript-eslint/eslint-plugin@8.8.1': + resolution: {integrity: sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -419,8 +445,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.0.0': - resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + '@typescript-eslint/parser@8.8.1': + resolution: {integrity: sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -429,12 +455,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.0.0': - resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + '@typescript-eslint/scope-manager@8.8.1': + resolution: {integrity: sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.0.0': - resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + '@typescript-eslint/type-utils@8.8.1': + resolution: {integrity: sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -442,12 +468,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.0.0': - resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + '@typescript-eslint/types@8.8.1': + resolution: {integrity: sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.0.0': - resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + '@typescript-eslint/typescript-estree@8.8.1': + resolution: {integrity: sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -455,14 +481,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.0.0': - resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + '@typescript-eslint/utils@8.8.1': + resolution: {integrity: sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.0.0': - resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + '@typescript-eslint/visitor-keys@8.8.1': + resolution: {integrity: sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -508,10 +534,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -645,8 +667,8 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -677,10 +699,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -729,20 +747,20 @@ packages: eslint-config-prettier: optional: true - eslint-scope@8.0.2: - resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + eslint-scope@8.1.0: + resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} 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-visitor-keys@4.0.0: - resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + eslint-visitor-keys@4.1.0: + resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.9.1: - resolution: {integrity: sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==} + eslint@9.12.0: + resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -751,8 +769,8 @@ packages: jiti: optional: true - espree@10.1.0: - resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + espree@10.2.0: + resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -887,10 +905,6 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -916,10 +930,6 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -970,10 +980,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -1179,8 +1185,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lemmy-js-client@0.19.5-alpha.1: - resolution: {integrity: sha512-GOhaiTQzrpwdmc3DFYemT2SmNmpuQJe2BWUms9QOzdYlkA1WZ0uu7axPE3s+T5OOxfy7K9Q2gsLe72dcVSlffw==} + lemmy-js-client@0.20.0-alpha.11: + resolution: {integrity: sha512-iRSG4xHMjPDIreQqVIoJ5JrMY71uk07G0Zbgyf068xKbib22J3+i1x/XgCTs6tiHlqTnw1Ig/KRq7p7qJoA4uw==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -1231,8 +1237,8 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mimic-fn@2.1.0: @@ -1250,8 +1256,8 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1324,12 +1330,8 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -1374,8 +1376,8 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -1412,6 +1414,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -1556,8 +1563,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript-eslint@8.0.0: - resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + typescript-eslint@8.8.1: + resolution: {integrity: sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -1565,8 +1572,8 @@ packages: typescript: optional: true - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true @@ -1635,17 +1642,17 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - '@babel/code-frame@7.23.5': + '@babel/code-frame@7.24.7': dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 + '@babel/highlight': 7.24.7 + picocolors: 1.1.0 '@babel/compat-data@7.23.5': {} '@babel/core@7.23.9': dependencies: '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.23.6 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) @@ -1655,7 +1662,7 @@ snapshots: '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1715,6 +1722,8 @@ snapshots: '@babel/helper-validator-identifier@7.22.20': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-option@7.23.5': {} '@babel/helpers@7.23.9': @@ -1725,11 +1734,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/highlight@7.23.4': + '@babel/highlight@7.24.7': dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 + picocolors: 1.1.0 '@babel/parser@7.23.9': dependencies: @@ -1807,13 +1817,13 @@ snapshots: '@babel/template@7.23.9': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 '@babel/traverse@7.23.9': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 @@ -1821,7 +1831,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - debug: 4.3.6 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -1834,26 +1844,28 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@eslint-community/eslint-utils@4.4.0(eslint@9.9.1)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.12.0)': dependencies: - eslint: 9.9.1 + eslint: 9.12.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.11.1': {} '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.6 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color + '@eslint/core@0.6.0': {} + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.6 - espree: 10.1.0 + debug: 4.3.7 + espree: 10.2.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -1863,13 +1875,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.9.1': {} + '@eslint/js@9.12.0': {} '@eslint/object-schema@2.1.4': {} + '@eslint/plugin-kit@0.2.0': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.0': {} + + '@humanfs/node@0.16.5': + dependencies: + '@humanfs/core': 0.19.0 + '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.0': {} + '@humanwhocodes/retry@0.3.1': {} '@istanbuljs/load-nyc-config@1.1.0': dependencies: @@ -1884,7 +1907,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -1897,14 +1920,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 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@22.5.1) + jest-config: 29.7.0(@types/node@22.7.4) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -1929,7 +1952,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -1947,7 +1970,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.5.1 + '@types/node': 22.7.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -1969,7 +1992,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.22 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2039,7 +2062,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.5.1 + '@types/node': 22.7.4 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -2105,9 +2128,11 @@ snapshots: dependencies: '@babel/types': 7.23.9 + '@types/estree@1.0.6': {} + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.5.1 + '@types/node': 22.7.4 '@types/istanbul-lib-coverage@2.0.6': {} @@ -2119,12 +2144,14 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/jest@29.5.12': + '@types/jest@29.5.13': dependencies: expect: 29.7.0 pretty-format: 29.7.0 - '@types/node@22.5.1': + '@types/json-schema@7.0.15': {} + + '@types/node@22.7.4': dependencies: undici-types: 6.19.8 @@ -2136,85 +2163,85 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2)': dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.0.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/type-utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.0.0 - eslint: 9.9.1 + '@eslint-community/regexpp': 4.11.1 + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/type-utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.8.1 + eslint: 9.12.0 graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.0.0 - debug: 4.3.6 - eslint: 9.9.1 + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.8.1 + debug: 4.3.7 + eslint: 9.12.0 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.0.0': + '@typescript-eslint/scope-manager@8.8.1': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/visitor-keys': 8.8.1 - '@typescript-eslint/type-utils@8.0.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.8.1(eslint@9.12.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) - debug: 4.3.6 - ts-api-utils: 1.3.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + debug: 4.3.7 + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.0.0': {} + '@typescript-eslint/types@8.8.1': {} - '@typescript-eslint/typescript-estree@8.0.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.8.1(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 - debug: 4.3.6 - globby: 11.1.0 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/visitor-keys': 8.8.1 + debug: 4.3.7 + fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.0.0(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/utils@8.8.1(eslint@9.12.0)(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - eslint: 9.9.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) + eslint: 9.12.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.0.0': + '@typescript-eslint/visitor-keys@8.8.1': dependencies: - '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/types': 8.8.1 eslint-visitor-keys: 3.4.3 acorn-jsx@5.3.2(acorn@8.12.1): @@ -2257,8 +2284,6 @@ snapshots: argparse@2.0.1: {} - array-union@2.1.0: {} - async@3.2.6: {} babel-jest@29.7.0(@babel/core@7.23.9): @@ -2400,13 +2425,13 @@ snapshots: convert-source-map@2.0.0: {} - create-jest@29.7.0(@types/node@22.5.1): + create-jest@29.7.0(@types/node@22.7.4): 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@22.5.1) + jest-config: 29.7.0(@types/node@22.7.4) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -2421,9 +2446,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.3.6: + debug@4.3.7: dependencies: - ms: 2.1.2 + ms: 2.1.3 dedent@1.5.1: {} @@ -2435,10 +2460,6 @@ snapshots: diff-sequences@29.6.3: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - ejs@3.1.10: dependencies: jake: 10.9.2 @@ -2461,40 +2482,44 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-prettier@5.2.1(eslint@9.9.1)(prettier@3.3.3): + eslint-plugin-prettier@5.2.1(eslint@9.12.0)(prettier@3.3.3): dependencies: - eslint: 9.9.1 + eslint: 9.12.0 prettier: 3.3.3 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 - eslint-scope@8.0.2: + eslint-scope@8.1.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.0.0: {} + eslint-visitor-keys@4.1.0: {} - eslint@9.9.1: + eslint@9.12.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.1) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) + '@eslint-community/regexpp': 4.11.1 '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.9.1 + '@eslint/js': 9.12.0 + '@eslint/plugin-kit': 0.2.0 + '@humanfs/node': 0.16.5 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.0 - '@nodelib/fs.walk': 1.2.8 + '@humanwhocodes/retry': 0.3.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.6 + debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint-scope: 8.0.2 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 + eslint-scope: 8.1.0 + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2504,23 +2529,20 @@ snapshots: ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color - espree@10.1.0: + espree@10.2.0: dependencies: acorn: 8.12.1 acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.0.0 + eslint-visitor-keys: 4.1.0 esprima@4.0.1: {} @@ -2568,7 +2590,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} @@ -2651,15 +2673,6 @@ snapshots: globals@14.0.0: {} - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -2676,8 +2689,6 @@ snapshots: human-signals@2.1.0: {} - ignore@5.3.1: {} - ignore@5.3.2: {} import-fresh@3.3.0: @@ -2717,8 +2728,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-stream@2.0.1: {} isexe@2.0.0: {} @@ -2753,7 +2762,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.6 + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -2783,7 +2792,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -2803,16 +2812,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.5.1): + jest-cli@29.7.0(@types/node@22.7.4): 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@22.5.1) + create-jest: 29.7.0(@types/node@22.7.4) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.5.1) + jest-config: 29.7.0(@types/node@22.7.4) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -2822,7 +2831,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.5.1): + jest-config@29.7.0(@types/node@22.7.4): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -2847,7 +2856,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.5.1 + '@types/node': 22.7.4 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -2876,7 +2885,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2886,7 +2895,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.5.1 + '@types/node': 22.7.4 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -2912,12 +2921,12 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.5 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -2925,7 +2934,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -2960,7 +2969,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2988,7 +2997,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -3027,14 +3036,14 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.6.2 transitivePeerDependencies: - supports-color jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -3053,7 +3062,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.5.1 + '@types/node': 22.7.4 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3062,17 +3071,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.5.1 + '@types/node': 22.7.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.5.1): + jest@29.7.0(@types/node@22.7.4): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.5.1) + jest-cli: 29.7.0(@types/node@22.7.4) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3108,7 +3117,7 @@ snapshots: kleur@3.0.3: {} - lemmy-js-client@0.19.5-alpha.1: {} + lemmy-js-client@0.20.0-alpha.11: {} leven@3.1.0: {} @@ -3154,7 +3163,7 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -3173,7 +3182,7 @@ snapshots: dependencies: brace-expansion: 2.0.1 - ms@2.1.2: {} + ms@2.1.3: {} natural-compare@1.4.0: {} @@ -3228,7 +3237,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -3241,9 +3250,7 @@ snapshots: path-parse@1.0.7: {} - path-type@4.0.0: {} - - picocolors@1.0.0: {} + picocolors@1.1.0: {} picomatch@2.3.1: {} @@ -3265,7 +3272,7 @@ snapshots: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 prompts@2.4.2: dependencies: @@ -3278,7 +3285,7 @@ snapshots: queue-microtask@1.2.3: {} - react-is@18.2.0: {} + react-is@18.3.1: {} require-directory@2.1.1: {} @@ -3306,6 +3313,8 @@ snapshots: semver@6.3.1: {} + semver@7.6.2: {} + semver@7.6.3: {} shebang-command@2.0.0: @@ -3389,22 +3398,22 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 - ts-jest@29.2.5(@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@22.5.1))(typescript@5.5.4): + ts-jest@29.2.5(@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@22.7.4))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.5.1) + jest: 29.7.0(@types/node@22.7.4) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.5.4 + typescript: 5.6.2 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.23.9 @@ -3422,18 +3431,18 @@ snapshots: type-fest@0.21.3: {} - typescript-eslint@8.0.0(eslint@9.9.1)(typescript@5.5.4): + typescript-eslint@8.8.1(eslint@9.12.0)(typescript@5.6.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/parser': 8.0.0(eslint@9.9.1)(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - eslint - supports-color - typescript@5.5.4: {} + typescript@5.6.2: {} undici-types@6.19.8: {} @@ -3441,7 +3450,7 @@ snapshots: dependencies: browserslist: 4.22.3 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.1.0 uri-js@4.4.1: dependencies: diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 8c3a23ab5..153405820 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -92,7 +92,7 @@ test("Create a comment", async () => { test("Create a comment in a non-existent post", async () => { await expect(createComment(alpha, -1)).rejects.toStrictEqual( - Error("couldnt_find_post"), + Error("not_found"), ); }); @@ -143,7 +143,7 @@ test("Delete a comment", async () => { await waitUntil( () => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), - r => r.message !== "couldnt_find_object", + r => r.message !== "not_found", ) ).comment; if (!gammaComment) { @@ -158,16 +158,16 @@ test("Delete a comment", async () => { expect(deleteCommentRes.comment_view.comment.deleted).toBe(true); expect(deleteCommentRes.comment_view.comment.content).toBe(""); - // Make sure that comment is undefined on beta + // Make sure that comment is deleted on beta await waitUntil( - () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), - e => e.message == "couldnt_find_object", + () => resolveComment(beta, commentRes.comment_view.comment), + c => c.comment?.comment.deleted === true, ); - // Make sure that comment is undefined on gamma after delete + // Make sure that comment is deleted on gamma after delete await waitUntil( - () => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), - e => e.message === "couldnt_find_object", + () => resolveComment(gamma, commentRes.comment_view.comment), + c => c.comment?.comment.deleted === true, ); // Test undeleting the comment @@ -181,11 +181,10 @@ test("Delete a comment", async () => { // Make sure that comment is undeleted on beta let betaComment2 = ( await waitUntil( - () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), - e => e.message !== "couldnt_find_object", + () => resolveComment(beta, commentRes.comment_view.comment), + c => c.comment?.comment.deleted === false, ) ).comment; - expect(betaComment2?.comment.deleted).toBe(false); assertCommentFederation(betaComment2, undeleteCommentRes.comment_view); }); @@ -858,3 +857,26 @@ test("Dont send a comment reply to a blocked community", async () => { blockRes = await blockCommunity(beta, newCommunityId, false); expect(blockRes.blocked).toBe(false); }); + +/// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also +/// fetched recursively. Ensure that it works properly. +test("Fetch a deeply nested comment", async () => { + let lastComment; + for (let i = 0; i < 50; i++) { + let commentRes = await createComment( + alpha, + postOnAlphaRes.post_view.post.id, + lastComment?.comment_view.comment.id, + ); + expect(commentRes.comment_view.comment).toBeDefined(); + lastComment = commentRes; + } + + let betaComment = await resolveComment( + beta, + lastComment!.comment_view.comment, + ); + + expect(betaComment!.comment!.comment).toBeDefined(); + expect(betaComment?.comment?.post).toBeDefined(); +}); diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index d8aa6cad6..77b68e2fc 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -527,12 +527,12 @@ test("Content in local-only community doesn't federate", async () => { // cant resolve the community from another instance await expect( resolveCommunity(beta, communityRes.actor_id), - ).rejects.toStrictEqual(Error("couldnt_find_object")); + ).rejects.toStrictEqual(Error("not_found")); // create a post, also cant resolve it let postRes = await createPost(alpha, communityRes.id); await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual( - Error("couldnt_find_object"), + Error("not_found"), ); }); diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 6b5c8d812..59e3d774e 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -125,12 +125,12 @@ test("Create a post", async () => { // Delta only follows beta, so it should not see an alpha ap_id await expect( resolvePost(delta, postRes.post_view.post), - ).rejects.toStrictEqual(Error("couldnt_find_object")); + ).rejects.toStrictEqual(Error("not_found")); // Epsilon has alpha blocked, it should not see the alpha post await expect( resolvePost(epsilon, postRes.post_view.post), - ).rejects.toStrictEqual(Error("couldnt_find_object")); + ).rejects.toStrictEqual(Error("not_found")); // remove added allow/blocklists editSiteForm.allowed_instances = []; @@ -140,9 +140,7 @@ test("Create a post", async () => { }); test("Create a post in a non-existent community", async () => { - await expect(createPost(alpha, -2)).rejects.toStrictEqual( - Error("couldnt_find_community"), - ); + await expect(createPost(alpha, -2)).rejects.toStrictEqual(Error("not_found")); }); test("Unlike a post", async () => { @@ -502,10 +500,17 @@ test("Enforce site ban federation for local user", async () => { alpha, alphaPerson.person.id, false, - false, + true, ); expect(unBanAlpha.banned).toBe(false); + // existing alpha post should be restored on beta + betaBanRes = await waitUntil( + () => getPost(beta, searchBeta1.post.id), + s => !s.post_view.post.removed, + ); + expect(betaBanRes.post_view.post.removed).toBe(false); + // Login gets invalidated by ban, need to login again if (!alphaUserPerson) { throw "Missing alpha person"; @@ -623,7 +628,7 @@ test("Enforce community ban for federated user", async () => { // Alpha tries to make post on beta, but it fails because of ban await expect( createPost(alpha, betaCommunity.community.id), - ).rejects.toStrictEqual(Error("banned_from_community")); + ).rejects.toStrictEqual(Error("person_is_banned_from_community")); // Unban alpha let unBanAlpha = await banPersonFromCommunity( @@ -789,3 +794,29 @@ test("Fetch post with redirect", async () => { let gammaPost2 = await gamma.resolveObject(form); expect(gammaPost2.post).toBeDefined(); }); + +test("Rewrite markdown links", async () => { + const community = (await resolveBetaCommunity(beta)).community!; + + // create a post + let postRes1 = await createPost(beta, community.community.id); + + // link to this post in markdown + let postRes2 = await createPost( + beta, + community.community.id, + "https://example.com/", + `[link](${postRes1.post_view.post.ap_id})`, + ); + console.log(postRes2.post_view.post.body); + expect(postRes2.post_view.post).toBeDefined(); + + // fetch both posts from another instance + const alphaPost1 = await resolvePost(alpha, postRes1.post_view.post); + const alphaPost2 = await resolvePost(alpha, postRes2.post_view.post); + + // remote markdown link is replaced with local link + expect(alphaPost2.post?.post.body).toBe( + `[link](http://lemmy-alpha:8541/post/${alphaPost1.post?.post.id})`, + ); +}); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 3ca37dac4..8ec4b29ed 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -419,13 +419,13 @@ export async function banPersonFromSite( api: LemmyHttp, person_id: number, ban: boolean, - remove_data: boolean, + remove_or_restore_data: boolean, ): Promise { // Make sure lemmy-beta/c/main is cached on lemmy_alpha let form: BanPerson = { person_id, ban, - remove_data, + remove_or_restore_data, }; return api.banPerson(form); } @@ -434,13 +434,13 @@ export async function banPersonFromCommunity( api: LemmyHttp, person_id: number, community_id: number, - remove_data: boolean, + remove_or_restore_data: boolean, ban: boolean, ): Promise { let form: BanFromCommunity = { person_id, community_id, - remove_data: remove_data, + remove_or_restore_data, ban, }; return api.banFromCommunity(form); @@ -690,7 +690,7 @@ export async function saveUserSettingsBio( blur_nsfw: false, auto_expand: true, theme: "darkly", - default_sort_type: "Active", + default_post_sort_type: "Active", default_listing_type: "All", interface_language: "en", show_avatars: true, @@ -710,7 +710,7 @@ export async function saveUserSettingsFederated( show_nsfw: false, blur_nsfw: true, auto_expand: false, - default_sort_type: "Hot", + default_post_sort_type: "Hot", default_listing_type: "All", interface_language: "", avatar, diff --git a/config/defaults.hjson b/config/defaults.hjson index 4bce48b5f..f0b9d56df 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -75,6 +75,8 @@ "ProxyAllImages" # Timeout for uploading images to pictrs (in seconds) upload_timeout: 30 + # Resize post thumbnails to this maximum width/height. + max_thumbnail_size: 256 } # Email sending configuration. All options except login/password are mandatory email: { diff --git a/crates/api/src/comment/distinguish.rs b/crates/api/src/comment/distinguish.rs index 0683af9a4..a1b25ea44 100644 --- a/crates/api/src/comment/distinguish.rs +++ b/crates/api/src/comment/distinguish.rs @@ -22,8 +22,7 @@ pub async fn distinguish_comment( data.comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; check_community_user_action( &local_user_view.person, @@ -60,8 +59,7 @@ pub async fn distinguish_comment( data.comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; Ok(Json(CommentResponse { comment_view, diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index b8a1c6f76..e93b8513f 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, CreateCommentLike}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_bot_account, check_community_user_action, check_downvotes_enabled}, + utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem}, }; use lemmy_db_schema::{ newtypes::LocalUserId, @@ -27,21 +27,26 @@ pub async fn like_comment( local_user_view: LocalUserView, ) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; + let comment_id = data.comment_id; let mut recipient_ids = Vec::::new(); - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + check_local_vote_mode( + data.score, + VoteItem::Comment(comment_id), + &local_site, + local_user_view.person.id, + &mut context.pool(), + ) + .await?; check_bot_account(&local_user_view.person)?; - let comment_id = data.comment_id; let orig_comment = CommentView::read( &mut context.pool(), comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; check_community_user_action( &local_user_view.person, @@ -54,8 +59,7 @@ pub async fn like_comment( let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await; if let Ok(Some(reply)) = comment_reply { let recipient_id = reply.recipient_id; - if let Ok(Some(local_recipient)) = - LocalUserView::read_person(&mut context.pool(), recipient_id).await + if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await { recipient_ids.push(local_recipient.local_user.id); } @@ -63,7 +67,6 @@ pub async fn like_comment( let like_form = CommentLikeForm { comment_id: data.comment_id, - post_id: orig_comment.post.id, person_id: local_user_view.person.id, score: data.score, }; diff --git a/crates/api/src/comment/list_comment_likes.rs b/crates/api/src/comment/list_comment_likes.rs index 4b2e1c8b3..c9721b8a0 100644 --- a/crates/api/src/comment/list_comment_likes.rs +++ b/crates/api/src/comment/list_comment_likes.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ utils::is_mod_or_admin, }; use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; /// Lists likes for a comment #[tracing::instrument(skip(context))] @@ -19,8 +19,7 @@ pub async fn list_comment_likes( data.comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; is_mod_or_admin( &mut context.pool(), diff --git a/crates/api/src/comment/save.rs b/crates/api/src/comment/save.rs index 67c2db331..6efa6296d 100644 --- a/crates/api/src/comment/save.rs +++ b/crates/api/src/comment/save.rs @@ -37,8 +37,7 @@ pub async fn save_comment( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; Ok(Json(CommentResponse { comment_view, diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index a269df07f..a0ff4be77 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -40,8 +40,7 @@ pub async fn create_comment_report( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; check_community_user_action( &local_user_view.person, @@ -64,9 +63,8 @@ pub async fn create_comment_report( .await .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; - let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommentReport)?; + let comment_report_view = + CommentReportView::read(&mut context.pool(), report.id, person_id).await?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index 40aad9569..a663fdf74 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -17,9 +17,7 @@ pub async fn resolve_comment_report( ) -> LemmyResult> { let report_id = data.report_id; let person_id = local_user_view.person.id; - let report = CommentReportView::read(&mut context.pool(), report_id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommentReport)?; + let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?; let person_id = local_user_view.person.id; check_community_mod_action( @@ -41,9 +39,8 @@ pub async fn resolve_comment_report( } let report_id = data.report_id; - let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommentReport)?; + let comment_report_view = + CommentReportView::read(&mut context.pool(), report_id, person_id).await?; Ok(Json(CommentReportResponse { comment_report_view, diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index 8d8826cd2..7d04f6bb0 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -46,23 +46,18 @@ pub async fn add_mod_to_community( .await?; } - let community = Community::read(&mut context.pool(), community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), community_id).await?; // If user is admin and community is remote, explicitly check that he is a // moderator. This is necessary because otherwise the action would be rejected // by the community's home instance. if local_user_view.local_user.admin && !community.local { - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community.id, local_user_view.person.id, ) .await?; - if !is_mod { - Err(LemmyErrorType::NotAModerator)? - } } // Update in local database diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 8e527d2ac..64b1c7196 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -4,7 +4,11 @@ use lemmy_api_common::{ community::{BanFromCommunity, BanFromCommunityResponse}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community}, + utils::{ + check_community_mod_action, + check_expire_time, + remove_or_restore_user_data_in_community, + }, }; use lemmy_db_schema::{ source::{ @@ -33,7 +37,6 @@ pub async fn ban_from_community( local_user_view: LocalUserView, ) -> LemmyResult> { let banned_person_id = data.person_id; - let remove_data = data.remove_data.unwrap_or(false); let expires = check_expire_time(data.expires)?; // Verify that only mods or admins can ban @@ -85,9 +88,18 @@ pub async fn ban_from_community( } // Remove/Restore their data if that's desired - if remove_data { - remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?; - } + if data.remove_or_restore_data.unwrap_or(false) { + let remove_data = data.ban; + remove_or_restore_user_data_in_community( + data.community_id, + local_user_view.person.id, + banned_person_id, + remove_data, + &data.reason, + &mut context.pool(), + ) + .await?; + }; // Mod tables let form = ModBanFromCommunityForm { @@ -101,9 +113,7 @@ pub async fn ban_from_community( ModBanFromCommunity::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), data.person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person_view = PersonView::read(&mut context.pool(), data.person_id).await?; ActivityChannel::submit_activity( SendActivityData::BanFromCommunity { diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index ad31548ea..90931c762 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -56,8 +56,7 @@ pub async fn block_community( Some(&local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; ActivityChannel::submit_activity( SendActivityData::FollowCommunity( diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index 2236fa5bc..d0f5bbf0d 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -23,9 +23,7 @@ pub async fn follow_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let community = Community::read(&mut context.pool(), data.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), data.community_id).await?; let mut community_follower_form = CommunityFollowerForm { community_id: community.id, person_id: local_user_view.person.id, @@ -68,8 +66,7 @@ pub async fn follow_community( Some(&local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; diff --git a/crates/api/src/community/mod.rs b/crates/api/src/community/mod.rs index 478192229..54bdbef28 100644 --- a/crates/api/src/community/mod.rs +++ b/crates/api/src/community/mod.rs @@ -3,4 +3,5 @@ pub mod ban; pub mod block; pub mod follow; pub mod hide; +pub mod random; pub mod transfer; diff --git a/crates/api/src/community/random.rs b/crates/api/src/community/random.rs new file mode 100644 index 000000000..3cc04e126 --- /dev/null +++ b/crates/api/src/community/random.rs @@ -0,0 +1,55 @@ +use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; +use lemmy_api_common::{ + community::{CommunityResponse, GetRandomCommunity}, + context::LemmyContext, + utils::{check_private_instance, is_mod_or_admin_opt}, +}; +use lemmy_db_schema::source::{ + actor_language::CommunityLanguage, + community::Community, + local_site::LocalSite, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommunityView; +use lemmy_utils::error::LemmyResult; + +#[tracing::instrument(skip(context))] +pub async fn get_random_community( + data: Query, + context: Data, + local_user_view: Option, +) -> LemmyResult> { + let local_site = LocalSite::read(&mut context.pool()).await?; + + check_private_instance(&local_user_view, &local_site)?; + + let local_user = local_user_view.as_ref().map(|u| &u.local_user); + + let random_community_id = + Community::get_random_community_id(&mut context.pool(), &data.type_).await?; + + let is_mod_or_admin = is_mod_or_admin_opt( + &mut context.pool(), + local_user_view.as_ref(), + Some(random_community_id), + ) + .await + .is_ok(); + + let community_view = CommunityView::read( + &mut context.pool(), + random_community_id, + local_user, + is_mod_or_admin, + ) + .await?; + + let discussion_languages = + CommunityLanguage::read(&mut context.pool(), random_community_id).await?; + + Ok(Json(CommunityResponse { + community_view, + discussion_languages, + })) +} diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index a32c069b1..195adbd8d 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -82,13 +82,10 @@ pub async fn transfer_community( Some(&local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; let community_id = data.community_id; - let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; // Return the jwt Ok(Json(GetCommunityResponse { diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 2b8e12d37..6ffa52f77 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -172,7 +172,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities( target: &Person, ban: bool, reason: &Option, - remove_data: &Option, + remove_or_restore_data: &Option, expires: &Option, context: &Data, ) -> LemmyResult<()> { @@ -230,7 +230,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities( person_id: target.id, ban, reason: reason.clone(), - remove_data: *remove_data, + remove_or_restore_data: *remove_or_restore_data, expires: *expires, }; @@ -258,17 +258,13 @@ pub async fn local_user_view_from_jwt( let local_user_id = Claims::validate(jwt, context) .await .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; - let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; check_user_valid(&local_user_view.person)?; Ok(local_user_view) } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs index 44b36fe66..299c9477a 100644 --- a/crates/api/src/local_user/add_admin.rs +++ b/crates/api/src/local_user/add_admin.rs @@ -36,8 +36,8 @@ pub async fn add_admin( // Make sure that the person_id added is local let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id) - .await? - .ok_or(LemmyErrorType::ObjectNotLocal)?; + .await + .map_err(|_| LemmyErrorType::ObjectNotLocal)?; LocalUser::update( &mut context.pool(), diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 58392cefd..2ace7f031 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ context::LemmyContext, person::{BanPerson, BanPersonResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_expire_time, is_admin, remove_user_data}, + utils::{check_expire_time, is_admin, remove_or_restore_user_data}, }; use lemmy_db_schema::{ source::{ @@ -60,15 +60,22 @@ pub async fn ban_from_site( // if its a local user, invalidate logins let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await; - if let Ok(Some(local_user)) = local_user { + if let Ok(local_user) = local_user { LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?; } // Remove their data if that's desired - let remove_data = data.remove_data.unwrap_or(false); - if remove_data { - remove_user_data(person.id, &context).await?; - } + if data.remove_or_restore_data.unwrap_or(false) { + let removed = data.ban; + remove_or_restore_user_data( + local_user_view.person.id, + person.id, + removed, + &data.reason, + &context, + ) + .await?; + }; // Mod tables let form = ModBanForm { @@ -81,16 +88,14 @@ pub async fn ban_from_site( ModBan::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), person.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person_view = PersonView::read(&mut context.pool(), person.id).await?; ban_nonlocal_user_from_local_communities( &local_user_view, &person, data.ban, &data.reason, - &data.remove_data, + &data.remove_or_restore_data, &data.expires, &context, ) @@ -101,7 +106,7 @@ pub async fn ban_from_site( moderator: local_user_view.person, banned_user: person_view.person.clone(), reason: data.reason.clone(), - remove_data: data.remove_data, + remove_or_restore_data: data.remove_or_restore_data, ban: data.ban, expires: data.expires, }, diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index 698703a9b..250277be3 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -32,8 +32,7 @@ pub async fn block_person( let target_user = LocalUserView::read_person(&mut context.pool(), target_id) .await - .ok() - .flatten(); + .ok(); if target_user.is_some_and(|t| t.local_user.admin) { Err(LemmyErrorType::CantBlockAdmin)? @@ -49,9 +48,7 @@ pub async fn block_person( .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } - let person_view = PersonView::read(&mut context.pool(), target_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person_view = PersonView::read(&mut context.pool(), target_id).await?; Ok(Json(BlockPersonResponse { person_view, blocked: data.block, diff --git a/crates/api/src/local_user/change_password.rs b/crates/api/src/local_user/change_password.rs index 50ee10bb6..03f873a0f 100644 --- a/crates/api/src/local_user/change_password.rs +++ b/crates/api/src/local_user/change_password.rs @@ -28,11 +28,13 @@ pub async fn change_password( } // Check the old password - let valid: bool = verify( - &data.old_password, - &local_user_view.local_user.password_encrypted, - ) - .unwrap_or(false); + let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted + { + verify(&data.old_password, password_encrypted).unwrap_or(false) + } else { + data.old_password.is_empty() + }; + if !valid { Err(LemmyErrorType::IncorrectLogin)? } 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 f86421796..191815d0f 100644 --- a/crates/api/src/local_user/change_password_after_reset.rs +++ b/crates/api/src/local_user/change_password_after_reset.rs @@ -21,7 +21,6 @@ pub async fn change_password_after_reset( let token = data.token.clone(); let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token) .await? - .ok_or(LemmyErrorType::TokenNotFound)? .local_user_id; password_length_check(&data.password)?; diff --git a/crates/api/src/local_user/generate_totp_secret.rs b/crates/api/src/local_user/generate_totp_secret.rs index f2bfe7f9c..03ba69759 100644 --- a/crates/api/src/local_user/generate_totp_secret.rs +++ b/crates/api/src/local_user/generate_totp_secret.rs @@ -2,8 +2,11 @@ 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}; -use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; -use lemmy_db_views::structs::{LocalUserView, SiteView}; +use lemmy_db_schema::source::{ + local_user::{LocalUser, LocalUserUpdateForm}, + site::Site, +}; +use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; /// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp] @@ -13,17 +16,14 @@ pub async fn generate_totp_secret( local_user_view: LocalUserView, context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site = Site::read_local(&mut context.pool()).await?; if local_user_view.local_user.totp_2fa_enabled { return Err(LemmyErrorType::TotpAlreadyEnabled)?; } let secret = generate_totp_2fa_secret(); - let secret_url = - build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url(); + let secret_url = build_totp_2fa(&site.name, &local_user_view.person.name, &secret)?.get_url(); let local_user_form = LocalUserUpdateForm { totp_2fa_secret: Some(Some(secret)), diff --git a/crates/api/src/local_user/login.rs b/crates/api/src/local_user/login.rs index 19f84f703..0b2514c5b 100644 --- a/crates/api/src/local_user/login.rs +++ b/crates/api/src/local_user/login.rs @@ -1,4 +1,4 @@ -use crate::{check_totp_2fa_valid, local_user::check_email_verified}; +use crate::check_totp_2fa_valid; use actix_web::{ web::{Data, Json}, HttpRequest, @@ -8,12 +8,7 @@ use lemmy_api_common::{ claims::Claims, context::LemmyContext, person::{Login, LoginResponse}, - utils::check_user_valid, -}; -use lemmy_db_schema::{ - source::{local_site::LocalSite, registration_application::RegistrationApplication}, - utils::DbPool, - RegistrationMode, + utils::{check_email_verified, check_registration_application, check_user_valid}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; @@ -24,23 +19,20 @@ pub async fn login( req: HttpRequest, context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; // Fetch that username / email let username_or_email = data.username_or_email.clone(); let local_user_view = - LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email) - .await? - .ok_or(LemmyErrorType::IncorrectLogin)?; + LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email).await?; // Verify the password - let valid: bool = verify( - &data.password, - &local_user_view.local_user.password_encrypted, - ) - .unwrap_or(false); + let valid: bool = local_user_view + .local_user + .password_encrypted + .as_ref() + .and_then(|password_encrypted| verify(&data.password, password_encrypted).ok()) + .unwrap_or(false); if !valid { Err(LemmyErrorType::IncorrectLogin)? } @@ -67,28 +59,3 @@ pub async fn login( registration_created: false, })) } - -async fn check_registration_application( - local_user_view: &LocalUserView, - local_site: &LocalSite, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - if (local_site.registration_mode == RegistrationMode::RequireApplication - || local_site.registration_mode == RegistrationMode::Closed) - && !local_user_view.local_user.accepted_application - && !local_user_view.local_user.admin - { - // Fetch the registration application. If no admin id is present its still pending. Otherwise it - // was processed (either accepted or denied). - let local_user_id = local_user_view.local_user.id; - let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id) - .await? - .ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?; - if registration.admin_id.is_some() { - Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))? - } else { - Err(LemmyErrorType::RegistrationApplicationIsPending)? - } - } - Ok(()) -} diff --git a/crates/api/src/local_user/mod.rs b/crates/api/src/local_user/mod.rs index c00a4516e..b1ee7c0b6 100644 --- a/crates/api/src/local_user/mod.rs +++ b/crates/api/src/local_user/mod.rs @@ -1,6 +1,3 @@ -use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; - pub mod add_admin; pub mod ban_person; pub mod block; @@ -20,15 +17,3 @@ pub mod save_settings; pub mod update_totp; pub mod validate_auth; pub mod verify_email; - -/// Check if the user's email is verified if email verification is turned on -/// However, skip checking verification if the user is an admin -fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> { - if !local_user_view.local_user.admin - && site_view.local_site.require_email_verification - && !local_user_view.local_user.email_verified - { - Err(LemmyErrorType::EmailNotVerified)? - } - Ok(()) -} diff --git a/crates/api/src/local_user/notifications/mark_mention_read.rs b/crates/api/src/local_user/notifications/mark_mention_read.rs index 90c8efb6e..9a839b2b4 100644 --- a/crates/api/src/local_user/notifications/mark_mention_read.rs +++ b/crates/api/src/local_user/notifications/mark_mention_read.rs @@ -18,9 +18,7 @@ pub async fn mark_person_mention_as_read( local_user_view: LocalUserView, ) -> LemmyResult> { let person_mention_id = data.person_mention_id; - let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPersonMention)?; + let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?; if local_user_view.person.id != read_person_mention.recipient_id { Err(LemmyErrorType::CouldntUpdateComment)? @@ -39,9 +37,7 @@ pub async fn mark_person_mention_as_read( let person_mention_id = read_person_mention.id; let person_id = local_user_view.person.id; let person_mention_view = - PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)) - .await? - .ok_or(LemmyErrorType::CouldntFindPersonMention)?; + PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?; Ok(Json(PersonMentionResponse { person_mention_view, diff --git a/crates/api/src/local_user/notifications/mark_reply_read.rs b/crates/api/src/local_user/notifications/mark_reply_read.rs index fdcfa5727..5b263145f 100644 --- a/crates/api/src/local_user/notifications/mark_reply_read.rs +++ b/crates/api/src/local_user/notifications/mark_reply_read.rs @@ -18,9 +18,7 @@ pub async fn mark_reply_as_read( local_user_view: LocalUserView, ) -> LemmyResult> { let comment_reply_id = data.comment_reply_id; - let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommentReply)?; + let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?; if local_user_view.person.id != read_comment_reply.recipient_id { Err(LemmyErrorType::CouldntUpdateComment)? @@ -40,9 +38,7 @@ pub async fn mark_reply_as_read( let comment_reply_id = read_comment_reply.id; let person_id = local_user_view.person.id; let comment_reply_view = - CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)) - .await? - .ok_or(LemmyErrorType::CouldntFindCommentReply)?; + CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?; Ok(Json(CommentReplyResponse { comment_reply_view })) } diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index b6b113c07..5cf06f23a 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -1,9 +1,8 @@ -use crate::local_user::check_email_verified; use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, person::PasswordReset, - utils::send_password_reset_email, + utils::{check_email_verified, send_password_reset_email}, SuccessResponse, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; @@ -17,12 +16,10 @@ pub async fn reset_password( // Fetch that email let email = data.email.to_lowercase(); let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email) - .await? - .ok_or(LemmyErrorType::IncorrectLogin)?; + .await + .map_err(|_| LemmyErrorType::IncorrectLogin)?; - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; check_email_verified(&local_user_view, &site_view)?; // Email the pure token to the user. diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 13bdd3646..08820cadd 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -36,9 +36,7 @@ pub async fn save_user_settings( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let slur_regex = local_site_to_slur_regex(&site_view.local_site); let url_blocklist = get_url_blocklist(&context).await?; @@ -65,9 +63,7 @@ pub async fn save_user_settings( 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.deref() != email { - if LocalUser::is_email_taken(&mut context.pool(), email).await? { - return Err(LemmyErrorType::EmailAlreadyExists)?; - } + LocalUser::check_is_email_taken(&mut context.pool(), email).await?; send_verification_email( &local_user_view, email, @@ -104,7 +100,8 @@ pub async fn save_user_settings( let local_user_id = local_user_view.local_user.id; let person_id = local_user_view.person.id; let default_listing_type = data.default_listing_type; - let default_sort_type = data.default_sort_type; + let default_post_sort_type = data.default_post_sort_type; + let default_comment_sort_type = data.default_comment_sort_type; let person_form = PersonUpdateForm { display_name, @@ -133,9 +130,9 @@ pub async fn save_user_settings( send_notifications_to_email: data.send_notifications_to_email, show_nsfw: data.show_nsfw, blur_nsfw: data.blur_nsfw, - auto_expand: data.auto_expand, show_bot_accounts: data.show_bot_accounts, - default_sort_type, + default_post_sort_type, + default_comment_sort_type, default_listing_type, theme: data.theme.clone(), interface_language: data.interface_language.clone(), diff --git a/crates/api/src/local_user/verify_email.rs b/crates/api/src/local_user/verify_email.rs index 5b895ec7e..4b6a8c928 100644 --- a/crates/api/src/local_user/verify_email.rs +++ b/crates/api/src/local_user/verify_email.rs @@ -10,19 +10,15 @@ use lemmy_db_schema::source::{ local_user::{LocalUser, LocalUserUpdateForm}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::error::{LemmyErrorType, LemmyResult}; +use lemmy_utils::error::LemmyResult; pub async fn verify_email( data: Json, context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let token = data.token.clone(); - let verification = EmailVerification::read_for_token(&mut context.pool(), &token) - .await? - .ok_or(LemmyErrorType::TokenNotFound)?; + let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?; let form = LocalUserUpdateForm { // necessary in case this is a new signup @@ -39,9 +35,7 @@ pub async fn verify_email( // send out notification about registration application to admins if enabled if site_view.local_site.application_email_admins { - let local_user = LocalUserView::read(&mut context.pool(), local_user_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?; send_new_applicant_email_to_admins( &local_user.person.name, diff --git a/crates/api/src/post/feature.rs b/crates/api/src/post/feature.rs index ec99a3345..cb6e6c144 100644 --- a/crates/api/src/post/feature.rs +++ b/crates/api/src/post/feature.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ PostFeatureType, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn feature_post( @@ -25,9 +25,7 @@ pub async fn feature_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let orig_post = Post::read(&mut context.pool(), post_id).await?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index e6903fb3c..c81d9630a 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -8,8 +8,9 @@ use lemmy_api_common::{ utils::{ check_bot_account, check_community_user_action, - check_downvotes_enabled, + check_local_vote_mode, mark_post_as_read, + VoteItem, }, }; use lemmy_db_schema::{ @@ -31,16 +32,20 @@ pub async fn like_post( local_user_view: LocalUserView, ) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; + let post_id = data.post_id; - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + check_local_vote_mode( + data.score, + VoteItem::Post(post_id), + &local_site, + local_user_view.person.id, + &mut context.pool(), + ) + .await?; check_bot_account(&local_user_view.person)?; // Check for a community ban - let post_id = data.post_id; - let post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), post_id).await?; check_community_user_action( &local_user_view.person, @@ -70,9 +75,7 @@ pub async fn like_post( mark_post_as_read(person_id, post_id, &mut context.pool()).await?; - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; ActivityChannel::submit_activity( SendActivityData::LikePostOrComment { diff --git a/crates/api/src/post/list_post_likes.rs b/crates/api/src/post/list_post_likes.rs index b9b2106b7..a9b302f2e 100644 --- a/crates/api/src/post/list_post_likes.rs +++ b/crates/api/src/post/list_post_likes.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::post::Post, traits::Crud}; use lemmy_db_views::structs::{LocalUserView, VoteView}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; /// Lists likes for a post #[tracing::instrument(skip(context))] @@ -15,9 +15,7 @@ pub async fn list_post_likes( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let post = Post::read(&mut context.pool(), data.post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), data.post_id).await?; is_mod_or_admin( &mut context.pool(), &local_user_view.person, diff --git a/crates/api/src/post/lock.rs b/crates/api/src/post/lock.rs index 36f9c2a33..548947b78 100644 --- a/crates/api/src/post/lock.rs +++ b/crates/api/src/post/lock.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn lock_post( @@ -24,9 +24,7 @@ pub async fn lock_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let orig_post = Post::read(&mut context.pool(), post_id).await?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index 85dfc11e3..4549b62b1 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -40,8 +40,7 @@ pub async fn save_post( Some(&local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?; diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 72a40f70d..590c9af40 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -35,9 +35,7 @@ pub async fn create_post_report( let person_id = local_user_view.person.id; let post_id = data.post_id; - let post_view = PostView::read(&mut context.pool(), post_id, None, false) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?; check_community_user_action( &local_user_view.person, @@ -61,9 +59,7 @@ pub async fn create_post_report( .await .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; - let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPostReport)?; + let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index 428619674..a3cb85c6c 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -17,9 +17,7 @@ pub async fn resolve_post_report( ) -> LemmyResult> { let report_id = data.report_id; let person_id = local_user_view.person.id; - let report = PostReportView::read(&mut context.pool(), report_id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPostReport)?; + let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?; let person_id = local_user_view.person.id; check_community_mod_action( @@ -40,9 +38,7 @@ pub async fn resolve_post_report( .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } - let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPostReport)?; + let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?; Ok(Json(PostReportResponse { post_report_view })) } diff --git a/crates/api/src/private_message/mark_read.rs b/crates/api/src/private_message/mark_read.rs index 07e06fe21..7c213464b 100644 --- a/crates/api/src/private_message/mark_read.rs +++ b/crates/api/src/private_message/mark_read.rs @@ -18,9 +18,7 @@ pub async fn mark_pm_as_read( ) -> LemmyResult> { // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.recipient_id { Err(LemmyErrorType::CouldntUpdatePrivateMessage)? } @@ -39,9 +37,7 @@ pub async fn mark_pm_as_read( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; Ok(Json(PrivateMessageResponse { private_message_view: view, })) diff --git a/crates/api/src/private_message_report/create.rs b/crates/api/src/private_message_report/create.rs index 41ac592ae..de8ca390f 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/private_message_report/create.rs @@ -29,9 +29,7 @@ pub async fn create_pm_report( let person_id = local_user_view.person.id; let private_message_id = data.private_message_id; - let private_message = PrivateMessage::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; // Make sure that only the recipient of the private message can create a report if person_id != private_message.recipient_id { @@ -49,9 +47,8 @@ pub async fn create_pm_report( .await .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?; - let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?; + let private_message_report_view = + PrivateMessageReportView::read(&mut context.pool(), report.id).await?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/private_message_report/resolve.rs b/crates/api/src/private_message_report/resolve.rs index 27847eeaf..7d821a60c 100644 --- a/crates/api/src/private_message_report/resolve.rs +++ b/crates/api/src/private_message_report/resolve.rs @@ -28,9 +28,8 @@ pub async fn resolve_pm_report( .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } - let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?; + let private_message_report_view = + PrivateMessageReportView::read(&mut context.pool(), report_id).await?; Ok(Json(PrivateMessageReportResponse { private_message_report_view, diff --git a/crates/api/src/site/federated_instances.rs b/crates/api/src/site/federated_instances.rs index 66b0ff2c1..5943cfd9a 100644 --- a/crates/api/src/site/federated_instances.rs +++ b/crates/api/src/site/federated_instances.rs @@ -5,15 +5,13 @@ use lemmy_api_common::{ utils::build_federated_instances, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn get_federated_instances( context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let federated_instances = build_federated_instances(&site_view.local_site, &mut context.pool()).await?; diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs index e7a5464f3..97ad7e2e5 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -7,11 +7,12 @@ use lemmy_db_schema::{ local_site_url_blocklist::LocalSiteUrlBlocklist, local_user::{LocalUser, LocalUserUpdateForm}, moderator::{ModAdd, ModAddForm}, + oauth_provider::OAuthProvider, tagline::Tagline, }, traits::Crud, }; -use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; +use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ error::{LemmyErrorType, LemmyResult}, @@ -55,17 +56,14 @@ pub async fn leave_admin( ModAdd::create(&mut context.pool(), &form).await?; // Reread site and admins - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let admins = PersonView::admins(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; - let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?; - let custom_emojis = - CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?; + let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; + let tagline = Tagline::get_random(&mut context.pool()).await.ok(); Ok(Json(GetSiteResponse { site_view, @@ -74,8 +72,11 @@ pub async fn leave_admin( my_user: None, all_languages, discussion_languages, - taglines, - custom_emojis, + oauth_providers: Some(oauth_providers), + admin_oauth_providers: None, blocked_urls, + tagline, + taglines: vec![], + custom_emojis: vec![], })) } diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index 9f90aff99..b21ffbc80 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn purge_comment( @@ -35,8 +35,7 @@ pub async fn purge_comment( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; // Also check that you're a higher admin LocalUser::is_higher_admin_check( diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index 59eded6ad..bf06bd529 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn purge_community( @@ -31,9 +31,7 @@ pub async fn purge_community( is_admin(&local_user_view)?; // Read the community to get its images - let community = Community::read(&mut context.pool(), data.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), data.community_id).await?; // Also check that you're a higher admin than all the mods let community_mod_person_ids = diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index dc824b163..7ab573cbc 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn purge_person( @@ -36,9 +36,7 @@ pub async fn purge_person( ) .await?; - let person = Person::read(&mut context.pool(), data.person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person = Person::read(&mut context.pool(), data.person_id).await?; ban_nonlocal_user_from_local_communities( &local_user_view, @@ -77,7 +75,7 @@ pub async fn purge_person( moderator: local_user_view.person, banned_user: person, reason: data.reason.clone(), - remove_data: Some(true), + remove_or_restore_data: Some(true), ban: true, expires: None, }, diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index 6e512312f..d2cacdae1 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn purge_post( @@ -29,9 +29,7 @@ pub async fn purge_post( is_admin(&local_user_view)?; // Read the post to get the community_id - let post = Post::read(&mut context.pool(), data.post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), data.post_id).await?; // Also check that you're a higher admin LocalUser::is_higher_admin_check( diff --git a/crates/api/src/site/registration_applications/approve.rs b/crates/api/src/site/registration_applications/approve.rs index dcde78117..b8cd6c0ea 100644 --- a/crates/api/src/site/registration_applications/approve.rs +++ b/crates/api/src/site/registration_applications/approve.rs @@ -14,10 +14,7 @@ use lemmy_db_schema::{ utils::{diesel_string_update, get_conn}, }; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; pub async fn approve_registration_application( data: Json, @@ -61,9 +58,8 @@ pub async fn approve_registration_application( .await?; if data.approve { - let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let approved_local_user_view = + LocalUserView::read(&mut context.pool(), approved_user_id).await?; if approved_local_user_view.local_user.email.is_some() { // Email sending may fail, but this won't revert the application approval send_application_approved_email(&approved_local_user_view, context.settings()).await?; @@ -71,9 +67,8 @@ pub async fn approve_registration_application( }; // Read the view - let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id) - .await? - .ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?; + let registration_application = + RegistrationApplicationView::read(&mut context.pool(), app_id).await?; Ok(Json(RegistrationApplicationResponse { registration_application, diff --git a/crates/api/src/site/registration_applications/get.rs b/crates/api/src/site/registration_applications/get.rs index 2d5d6bf5b..23c6fb4d0 100644 --- a/crates/api/src/site/registration_applications/get.rs +++ b/crates/api/src/site/registration_applications/get.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ utils::is_admin, }; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; /// Lists registration applications, filterable by undenied only. pub async fn get_registration_application( @@ -18,9 +18,7 @@ pub async fn get_registration_application( // Read the view let registration_application = - RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?; + RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id).await?; Ok(Json(RegistrationApplicationResponse { registration_application, diff --git a/crates/api/src/site/registration_applications/tests.rs b/crates/api/src/site/registration_applications/tests.rs index 062fa550f..022cbf236 100644 --- a/crates/api/src/site/registration_applications/tests.rs +++ b/crates/api/src/site/registration_applications/tests.rs @@ -34,13 +34,10 @@ use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API}; use serial_test::serial; -#[allow(clippy::unwrap_used)] async fn create_test_site(context: &Data) -> LemmyResult<(Instance, LocalUserView)> { let pool = &mut context.pool(); - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .expect("Create test instance"); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let admin_person = Person::create( pool, @@ -54,35 +51,26 @@ async fn create_test_site(context: &Data) -> LemmyResult<(Instance ) .await?; - let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id) - .await? - .unwrap(); + let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id).await?; - let site_form = SiteInsertForm::builder() - .name("test site".to_string()) - .instance_id(inserted_instance.id) - .build(); - let site = Site::create(pool, &site_form).await.unwrap(); + let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id); + let site = Site::create(pool, &site_form).await?; // Create a local site, since this is necessary for determining if email verification is // required - let local_site_form = LocalSiteInsertForm::builder() - .site_id(site.id) - .require_email_verification(Some(true)) - .application_question(Some(".".to_string())) - .registration_mode(Some(RegistrationMode::RequireApplication)) - .site_setup(Some(true)) - .build(); - let local_site = LocalSite::create(pool, &local_site_form).await.unwrap(); + let local_site_form = LocalSiteInsertForm { + require_email_verification: Some(true), + application_question: Some(".".to_string()), + registration_mode: Some(RegistrationMode::RequireApplication), + site_setup: Some(true), + ..LocalSiteInsertForm::new(site.id) + }; + let local_site = LocalSite::create(pool, &local_site_form).await?; // Required to have a working local SiteView when updating the site to change email verification // requirement or registration mode - let rate_limit_form = LocalSiteRateLimitInsertForm::builder() - .local_site_id(local_site.id) - .build(); - LocalSiteRateLimit::create(pool, &rate_limit_form) - .await - .unwrap(); + let rate_limit_form = LocalSiteRateLimitInsertForm::new(local_site.id); + LocalSiteRateLimit::create(pool, &rate_limit_form).await?; Ok((inserted_instance, admin_local_user_view)) } @@ -116,7 +104,6 @@ async fn signup( Ok((local_user, application)) } -#[allow(clippy::unwrap_used)] async fn get_application_statuses( context: &Data, admin: LocalUserView, @@ -129,14 +116,14 @@ async fn get_application_statuses( get_unread_registration_application_count(context.reset_request_count(), admin.clone()).await?; let unread_applications = list_registration_applications( - Query::from_query("unread_only=true").unwrap(), + Query::from_query("unread_only=true")?, context.reset_request_count(), admin.clone(), ) .await?; let all_applications = list_registration_applications( - Query::from_query("unread_only=false").unwrap(), + Query::from_query("unread_only=false")?, context.reset_request_count(), admin, ) @@ -145,10 +132,9 @@ async fn get_application_statuses( Ok((application_count, unread_applications, all_applications)) } -#[allow(clippy::indexing_slicing)] -#[allow(clippy::unwrap_used)] -#[tokio::test] #[serial] +#[tokio::test] +#[expect(clippy::indexing_slicing)] async fn test_application_approval() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; let pool = &mut context.pool(); diff --git a/crates/api/src/sitemap.rs b/crates/api/src/sitemap.rs index bd0e0dad8..c3c3c417c 100644 --- a/crates/api/src/sitemap.rs +++ b/crates/api/src/sitemap.rs @@ -42,44 +42,40 @@ pub async fn get_sitemap(context: Data) -> LemmyResult LemmyResult<()> { let posts: Vec<(DbUrl, DateTime)> = vec![ ( - Url::parse("https://example.com").unwrap().into(), + Url::parse("https://example.com")?.into(), NaiveDate::from_ymd_opt(2022, 12, 1) - .unwrap() + .unwrap_or_default() .and_hms_opt(9, 10, 11) - .unwrap() + .unwrap_or_default() .and_utc(), ), ( - Url::parse("https://lemmy.ml").unwrap().into(), + Url::parse("https://lemmy.ml")?.into(), NaiveDate::from_ymd_opt(2023, 1, 1) - .unwrap() + .unwrap_or_default() .and_hms_opt(1, 2, 3) - .unwrap() + .unwrap_or_default() .and_utc(), ), ]; let mut buf = Vec::::new(); - generate_urlset(posts) - .await - .unwrap() - .write(&mut buf) - .unwrap(); - let root = Element::from_reader(buf.as_slice()).unwrap(); + generate_urlset(posts).await?.write(&mut buf)?; + let root = Element::from_reader(buf.as_slice())?; assert_eq!(root.tag().name(), "urlset"); assert_eq!(root.child_count(), 2); @@ -99,45 +95,43 @@ pub(crate) mod tests { root .children() .next() - .unwrap() - .children() - .find(|element| element.tag().name() == "loc") - .unwrap() - .text(), + .and_then(|n| n.children().find(|element| element.tag().name() == "loc")) + .map(Element::text) + .unwrap_or_default(), "https://example.com/" ); assert_eq!( root .children() .next() - .unwrap() - .children() - .find(|element| element.tag().name() == "lastmod") - .unwrap() - .text(), + .and_then(|n| n + .children() + .find(|element| element.tag().name() == "lastmod")) + .map(Element::text) + .unwrap_or_default(), "2022-12-01T09:10:11+00:00" ); assert_eq!( root .children() .nth(1) - .unwrap() - .children() - .find(|element| element.tag().name() == "loc") - .unwrap() - .text(), + .and_then(|n| n.children().find(|element| element.tag().name() == "loc")) + .map(Element::text) + .unwrap_or_default(), "https://lemmy.ml/" ); assert_eq!( root .children() .nth(1) - .unwrap() - .children() - .find(|element| element.tag().name() == "lastmod") - .unwrap() - .text(), + .and_then(|n| n + .children() + .find(|element| element.tag().name() == "lastmod")) + .map(Element::text) + .unwrap_or_default(), "2023-01-01T01:02:03+00:00" ); + + Ok(()) } } diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index 8f140f2fe..d40f4c23d 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -27,7 +27,6 @@ use lemmy_db_views_actor::structs::CommunityView; use lemmy_utils::{ error::LemmyResult, utils::{markdown::markdown_to_html, mention::MentionData}, - LemmyErrorType, }; pub async fn build_comment_response( @@ -37,9 +36,8 @@ pub async fn build_comment_response( recipient_ids: Vec, ) -> LemmyResult { let local_user = local_user_view.map(|l| l.local_user); - let comment_view = CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let comment_view = + CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()).await?; Ok(CommentResponse { comment_view, recipient_ids, @@ -61,8 +59,7 @@ pub async fn build_community_response( Some(&local_user), is_mod_or_admin, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; Ok(Json(CommunityResponse { @@ -87,8 +84,7 @@ pub async fn build_post_response( Some(&local_user), is_mod_or_admin, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; Ok(Json(PostResponse { post_view })) } @@ -112,8 +108,7 @@ pub async fn send_local_notifs( comment_id, local_user_view.map(|view| &view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; let comment = comment_view.comment; let post = comment_view.post; let community = comment_view.community; @@ -125,7 +120,7 @@ pub async fn send_local_notifs( { let mention_name = mention.name.clone(); let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await; - if let Ok(Some(mention_user_view)) = user_view { + if let Ok(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 @@ -161,9 +156,7 @@ pub async fn send_local_notifs( // Send comment_reply to the parent commenter / poster if let Some(parent_comment_id) = comment.parent_comment_id() { - let parent_comment = Comment::read(&mut context.pool(), parent_comment_id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?; // Get the parent commenter local_user let parent_creator_id = parent_comment.creator_id; @@ -182,7 +175,7 @@ pub async fn send_local_notifs( // Don't send a notif to yourself if parent_comment.creator_id != person.id && !check_blocks { let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await; - if let Ok(Some(parent_user_view)) = user_view { + if let Ok(parent_user_view) = user_view { // Don't duplicate notif if already mentioned by checking recipient ids if !recipient_ids.contains(&parent_user_view.local_user.id) { recipient_ids.push(parent_user_view.local_user.id); @@ -229,7 +222,7 @@ pub async fn send_local_notifs( if post.creator_id != person.id && !check_blocks { let creator_id = post.creator_id; let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await; - if let Ok(Some(parent_user_view)) = parent_user { + if let Ok(parent_user_view) = parent_user { if !recipient_ids.contains(&parent_user_view.local_user.id) { recipient_ids.push(parent_user_view.local_user.id); diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index 905394785..6476f855a 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -29,12 +29,8 @@ impl Claims { let claims = decode::(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?; let user_id = LocalUserId(claims.claims.sub.parse()?); - let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?; - if !is_valid { - Err(LemmyErrorType::NotLoggedIn)? - } else { - Ok(user_id) - } + LoginToken::validate(&mut context.pool(), user_id, jwt).await?; + Ok(user_id) } pub async fn generate( @@ -73,8 +69,6 @@ impl Claims { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{claims::Claims, context::LemmyContext}; @@ -89,7 +83,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; - use lemmy_utils::rate_limit::RateLimitCell; + use lemmy_utils::{error::LemmyResult, rate_limit::RateLimitCell}; use pretty_assertions::assert_eq; use reqwest::Client; use reqwest_middleware::ClientBuilder; @@ -97,10 +91,10 @@ mod tests { #[tokio::test] #[serial] - async fn test_should_not_validate_user_token_after_password_change() { + async fn test_should_not_validate_user_token_after_password_change() -> LemmyResult<()> { let pool_ = build_db_pool_for_tests().await; let pool = &mut (&pool_).into(); - let secret = Secret::init(pool).await.unwrap().unwrap(); + let secret = Secret::init(pool).await?; let context = LemmyContext::create( pool_.clone(), ClientBuilder::new(Client::default()).build(), @@ -108,29 +102,25 @@ mod tests { RateLimitCell::with_test_config(), ); - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let local_user_form = LocalUserInsertForm::test_form(inserted_person.id); - let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![]) - .await - .unwrap(); + let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; let req = TestRequest::default().to_http_request(); - let jwt = Claims::generate(inserted_local_user.id, req, &context) - .await - .unwrap(); + let jwt = Claims::generate(inserted_local_user.id, req, &context).await?; let valid = Claims::validate(&jwt, &context).await; assert!(valid.is_ok()); - let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + let num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, num_deleted); + + Ok(()) } } diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index 9d306ff7a..f8e741a58 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -3,9 +3,13 @@ use lemmy_db_schema::{ source::site::Site, CommunityVisibility, ListingType, - SortType, }; -use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView}; +use lemmy_db_views_actor::structs::{ + CommunityModeratorView, + CommunitySortType, + CommunityView, + PersonView, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -74,7 +78,7 @@ pub struct CommunityResponse { /// Fetches a list of communities. pub struct ListCommunities { pub type_: Option, - pub sort: Option, + pub sort: Option, pub show_nsfw: Option, pub page: Option, pub limit: Option, @@ -97,7 +101,9 @@ pub struct BanFromCommunity { pub community_id: CommunityId, pub person_id: PersonId, pub ban: bool, - pub remove_data: Option, + /// Optionally remove or restore all their data. Useful for new troll accounts. + /// If ban is true, then this means remove. If ban is false, it means restore. + pub remove_or_restore_data: Option, pub reason: Option, /// A time that the ban will expire, in unix epoch seconds. /// @@ -223,3 +229,12 @@ pub struct TransferCommunity { pub community_id: CommunityId, pub person_id: PersonId, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Fetches a random community +pub struct GetRandomCommunity { + pub type_: Option, +} diff --git a/crates/api_common/src/custom_emoji.rs b/crates/api_common/src/custom_emoji.rs index 468d2128d..3804b71af 100644 --- a/crates/api_common/src/custom_emoji.rs +++ b/crates/api_common/src/custom_emoji.rs @@ -1,6 +1,7 @@ use lemmy_db_schema::newtypes::CustomEmojiId; use lemmy_db_views::structs::CustomEmojiView; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; #[cfg(feature = "full")] use ts_rs::TS; use url::Url; @@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji { pub struct CustomEmojiResponse { pub custom_emoji: CustomEmojiView, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A response for custom emojis. +pub struct ListCustomEmojisResponse { + pub custom_emojis: Vec, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Fetches a list of custom emojis. +pub struct ListCustomEmojis { + pub page: Option, + pub limit: Option, + pub category: Option, + pub ignore_page_limits: Option, +} diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 9d12d2e13..68eeadecc 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -7,6 +7,7 @@ pub mod community; #[cfg(feature = "full")] pub mod context; pub mod custom_emoji; +pub mod oauth_provider; pub mod person; pub mod post; pub mod private_message; @@ -15,6 +16,7 @@ pub mod request; #[cfg(feature = "full")] pub mod send_activity; pub mod site; +pub mod tagline; #[cfg(feature = "full")] pub mod utils; diff --git a/crates/api_common/src/oauth_provider.rs b/crates/api_common/src/oauth_provider.rs new file mode 100644 index 000000000..14847edf1 --- /dev/null +++ b/crates/api_common/src/oauth_provider.rs @@ -0,0 +1,71 @@ +use lemmy_db_schema::newtypes::OAuthProviderId; +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)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Create an external auth method. +pub struct CreateOAuthProvider { + pub display_name: String, + pub issuer: String, + pub authorization_endpoint: String, + pub token_endpoint: String, + pub userinfo_endpoint: String, + pub id_claim: String, + pub client_id: String, + pub client_secret: String, + pub scopes: String, + pub auto_verify_email: Option, + pub account_linking_enabled: Option, + pub enabled: Option, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Edit an external auth method. +pub struct EditOAuthProvider { + pub id: OAuthProviderId, + pub display_name: Option, + pub authorization_endpoint: Option, + pub token_endpoint: Option, + pub userinfo_endpoint: Option, + pub id_claim: Option, + pub client_secret: Option, + pub scopes: Option, + pub auto_verify_email: Option, + pub account_linking_enabled: Option, + pub enabled: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Delete an external auth method. +pub struct DeleteOAuthProvider { + pub id: OAuthProviderId, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Logging in with an OAuth 2.0 authorization +pub struct AuthenticateWithOauth { + pub code: String, + #[cfg_attr(feature = "full", ts(type = "string"))] + pub oauth_provider_id: OAuthProviderId, + #[cfg_attr(feature = "full", ts(type = "string"))] + pub redirect_uri: Url, + pub show_nsfw: Option, + /// Username is mandatory at registration time + pub username: Option, + /// An answer is mandatory if require application is enabled on the server + pub answer: Option, +} diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 29e9ef084..6f1ddfe43 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -5,7 +5,7 @@ use lemmy_db_schema::{ CommentSortType, ListingType, PostListingMode, - SortType, + PostSortType, }; use lemmy_db_views::structs::{CommentView, LocalImageView, PostView}; use lemmy_db_views_actor::structs::{ @@ -84,12 +84,18 @@ pub struct CaptchaResponse { pub struct SaveUserSettings { /// Show nsfw posts. pub show_nsfw: Option, + /// Blur nsfw posts. pub blur_nsfw: Option, - pub auto_expand: Option, /// Your user's theme. pub theme: Option, - pub default_sort_type: Option, + /// The default post listing type, usually "local" pub default_listing_type: Option, + /// A post-view mode that changes how multiple post listings look. + pub post_listing_mode: Option, + /// The default post sort, usually "active" + pub default_post_sort_type: Option, + /// The default comment sort, usually "hot" + pub default_comment_sort_type: Option, /// The language of the lemmy interface pub interface_language: Option, /// A URL for your avatar. @@ -120,8 +126,6 @@ pub struct SaveUserSettings { pub open_links_in_new_tab: Option, /// Enable infinite scroll pub infinite_scroll_enabled: Option, - /// A post-view mode that changes how multiple post listings look. - 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 @@ -172,7 +176,7 @@ pub struct GetPersonDetails { pub person_id: Option, /// Example: dessalines , or dessalines@xyz.tld pub username: Option, - pub sort: Option, + pub sort: Option, pub page: Option, pub limit: Option, pub community_id: Option, @@ -217,8 +221,9 @@ pub struct AddAdminResponse { pub struct BanPerson { pub person_id: PersonId, pub ban: bool, - /// Optionally remove all their data. Useful for new troll accounts. - pub remove_data: Option, + /// Optionally remove or restore all their data. Useful for new troll accounts. + /// If ban is true, then this means remove. If ban is false, it means restore. + pub remove_or_restore_data: Option, pub reason: Option, /// A time that the ban will expire, in unix epoch seconds. /// diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 74369173b..fa45459e2 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -2,7 +2,7 @@ use lemmy_db_schema::{ newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId}, ListingType, PostFeatureType, - SortType, + PostSortType, }; use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; @@ -30,6 +30,8 @@ pub struct CreatePost { pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. pub custom_thumbnail: Option, + /// Time when this post should be scheduled. Null means publish immediately. + pub scheduled_publish_time: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -69,7 +71,7 @@ pub struct GetPostResponse { /// Get a list of posts. pub struct GetPosts { pub type_: Option, - pub sort: Option, + pub sort: Option, /// DEPRECATED, use page_cursor pub page: Option, pub limit: Option, @@ -124,6 +126,8 @@ pub struct EditPost { pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. pub custom_thumbnail: Option, + /// Time when this post should be scheduled. Null means publish immediately. + pub scheduled_publish_time: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index de6ba4f39..b0da6cf4d 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -3,7 +3,7 @@ use crate::{ lemmy_db_schema::traits::Crud, post::{LinkMetadata, OpenGraphData}, send_activity::{ActivityChannel, SendActivityData}, - utils::{local_site_opt_to_sensitive, proxy_image_link}, + utils::proxy_image_link, }; use activitypub_federation::config::Data; use chrono::{DateTime, Utc}; @@ -13,8 +13,8 @@ use lemmy_db_schema::{ newtypes::DbUrl, source::{ images::{ImageDetailsForm, LocalImage, LocalImageForm}, - local_site::LocalSite, post::{Post, PostUpdateForm}, + site::Site, }, }; use lemmy_utils::{ @@ -44,6 +44,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder { .user_agent(user_agent.clone()) .timeout(REQWEST_TIMEOUT) .connect_timeout(REQWEST_TIMEOUT) + .use_rustls_tls() } /// Fetches metadata for the given link and optionally generates thumbnail. @@ -130,7 +131,6 @@ pub async fn generate_post_link_metadata( post: Post, custom_thumbnail: Option, send_activity: impl FnOnce(Post) -> Option + Send + 'static, - local_site: Option, context: Data, ) -> LemmyResult<()> { let metadata = match &post.url { @@ -144,7 +144,8 @@ pub async fn generate_post_link_metadata( .is_some_and(|content_type| content_type.starts_with("image")); // Decide if we are allowed to generate local thumbnail - let allow_sensitive = local_site_opt_to_sensitive(&local_site); + let site = Site::read_local(&mut context.pool()).await?; + let allow_sensitive = site.content_warning.is_some(); let allow_generate_thumbnail = allow_sensitive || !post.nsfw; let image_url = if is_image_post { @@ -353,9 +354,10 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L // fetch remote non-pictrs images for persistent thumbnail link // TODO: should limit size once supported by pictrs let fetch_url = format!( - "{}image/download?url={}", + "{}image/download?url={}&resize={}", pictrs_config.url, - encode(image_url.as_str()) + encode(image_url.as_str()), + context.settings().pictrs_config()?.max_thumbnail_size ); let res = context @@ -470,14 +472,13 @@ pub async fn replace_image( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ context::LemmyContext, request::{extract_opengraph_data, fetch_link_metadata}, }; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; use url::Url; @@ -485,10 +486,10 @@ mod tests { // These helped with testing #[tokio::test] #[serial] - async fn test_link_metadata() { + async fn test_link_metadata() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; - let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap(); - let sample_res = fetch_link_metadata(&sample_url, &context).await.unwrap(); + let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ")?; + let sample_res = fetch_link_metadata(&sample_url, &context).await?; assert_eq!( Some("FAQ · Wiki · IzzyOnDroid / repo · GitLab".to_string()), sample_res.opengraph_data.title @@ -499,8 +500,7 @@ mod tests { ); assert_eq!( Some( - Url::parse("https://gitlab.com/uploads/-/system/project/avatar/4877469/iod_logo.png") - .unwrap() + Url::parse("https://gitlab.com/uploads/-/system/project/avatar/4877469/iod_logo.png")? .into() ), sample_res.opengraph_data.image @@ -510,19 +510,21 @@ mod tests { Some(mime::TEXT_HTML_UTF_8.to_string()), sample_res.content_type ); + + Ok(()) } #[test] - fn test_resolve_image_url() { + fn test_resolve_image_url() -> LemmyResult<()> { // url that lists the opengraph fields - let url = Url::parse("https://example.com/one/two.html").unwrap(); + let url = Url::parse("https://example.com/one/two.html")?; // root relative url let html_bytes = b""; let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata"); assert_eq!( metadata.image, - Some(Url::parse("https://example.com/image.jpg").unwrap().into()) + Some(Url::parse("https://example.com/image.jpg")?.into()) ); // base relative url @@ -530,11 +532,7 @@ mod tests { let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata"); assert_eq!( metadata.image, - Some( - Url::parse("https://example.com/one/image.jpg") - .unwrap() - .into() - ) + Some(Url::parse("https://example.com/one/image.jpg")?.into()) ); // absolute url @@ -542,7 +540,7 @@ mod tests { let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata"); assert_eq!( metadata.image, - Some(Url::parse("https://cdn.host.com/image.jpg").unwrap().into()) + Some(Url::parse("https://cdn.host.com/image.jpg")?.into()) ); // protocol relative url @@ -550,7 +548,9 @@ mod tests { let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata"); assert_eq!( metadata.image, - Some(Url::parse("https://example.com/image.jpg").unwrap().into()) + Some(Url::parse("https://example.com/image.jpg")?.into()) ); + + Ok(()) } } diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs index 02518ca33..465e074f4 100644 --- a/crates/api_common/src/send_activity.rs +++ b/crates/api_common/src/send_activity.rs @@ -83,7 +83,7 @@ pub enum SendActivityData { moderator: Person, banned_user: Person, reason: Option, - remove_data: Option, + remove_or_restore_data: Option, ban: bool, expires: Option, }, diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 5311eca9c..8316b30ee 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -16,19 +16,21 @@ use lemmy_db_schema::{ instance::Instance, language::Language, local_site_url_blocklist::LocalSiteUrlBlocklist, + oauth_provider::{OAuthProvider, PublicOAuthProvider}, person::Person, tagline::Tagline, }, + CommentSortType, + FederationMode, ListingType, ModlogActionType, PostListingMode, + PostSortType, RegistrationMode, SearchType, - SortType, }; use lemmy_db_views::structs::{ CommentView, - CustomEmojiView, LocalUserView, PostView, RegistrationApplicationView, @@ -73,10 +75,15 @@ pub struct Search { pub community_name: Option, pub creator_id: Option, pub type_: Option, - pub sort: Option, + pub sort: Option, pub listing_type: Option, pub page: Option, pub limit: Option, + pub title_only: Option, + pub post_url_only: Option, + pub saved_only: Option, + pub liked_only: Option, + pub disliked_only: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -164,7 +171,6 @@ pub struct CreateSite { pub description: Option, pub icon: Option, pub banner: Option, - pub enable_downvotes: Option, pub enable_nsfw: Option, pub community_creation_admin_only: Option, pub require_email_verification: Option, @@ -172,7 +178,9 @@ pub struct CreateSite { pub private_instance: Option, pub default_theme: Option, pub default_post_listing_type: Option, - pub default_sort_type: Option, + pub default_post_listing_mode: Option, + pub default_post_sort_type: Option, + pub default_comment_sort_type: Option, pub legal_information: Option, pub application_email_admins: Option, pub hide_modlog_mod_names: Option, @@ -197,10 +205,13 @@ pub struct CreateSite { pub captcha_difficulty: Option, pub allowed_instances: Option>, pub blocked_instances: Option>, - pub taglines: Option>, pub registration_mode: Option, + pub oauth_registration: Option, pub content_warning: Option, - pub default_post_listing_mode: Option, + pub post_upvotes: Option, + pub post_downvotes: Option, + pub comment_upvotes: Option, + pub comment_downvotes: Option, } #[skip_serializing_none] @@ -217,8 +228,6 @@ pub struct EditSite { pub icon: Option, /// A url for your site's banner. pub banner: Option, - /// Whether to enable downvotes. - pub enable_downvotes: Option, /// Whether to enable NSFW. pub enable_nsfw: Option, /// Limits community creation to admins only. @@ -231,9 +240,14 @@ pub struct EditSite { pub private_instance: Option, /// The default theme. Usually "browser" pub default_theme: Option, + /// The default post listing type, usually "local" pub default_post_listing_type: Option, - /// The default sort, usually "active" - pub default_sort_type: Option, + /// Default value for listing mode, usually "list" + pub default_post_listing_mode: Option, + /// The default post sort, usually "active" + pub default_post_sort_type: Option, + /// The default comment sort, usually "hot" + pub default_comment_sort_type: Option, /// An optional page of legal information pub legal_information: Option, /// Whether to email admins when receiving a new application. @@ -278,16 +292,22 @@ pub struct EditSite { pub blocked_instances: Option>, /// A list of blocked URLs pub blocked_urls: Option>, - /// A list of taglines shown at the top of the front page. - pub taglines: Option>, pub registration_mode: Option, /// Whether to email admins for new reports. pub reports_email_admins: Option, /// If present, nsfw content is visible by default. Should be displayed by frontends/clients /// when the site is first opened by a user. pub content_warning: Option, - /// Default value for [LocalUser.post_listing_mode] - pub default_post_listing_mode: Option, + /// Whether or not external auth methods can auto-register users. + pub oauth_registration: Option, + /// What kind of post upvotes your site allows. + pub post_upvotes: Option, + /// What kind of post downvotes your site allows. + pub post_downvotes: Option, + /// What kind of comment upvotes your site allows. + pub comment_upvotes: Option, + /// What kind of comment downvotes your site allows. + pub comment_downvotes: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -296,7 +316,8 @@ pub struct EditSite { /// The response for a site. pub struct SiteResponse { pub site_view: SiteView, - pub taglines: Vec, + /// deprecated, use field `tagline` or /api/v3/tagline/list + pub taglines: Vec<()>, } #[skip_serializing_none] @@ -311,10 +332,15 @@ pub struct GetSiteResponse { pub my_user: Option, pub all_languages: Vec, pub discussion_languages: Vec, - /// A list of taglines shown at the top of the front page. - pub taglines: Vec, - /// A list of custom emojis your site supports. - pub custom_emojis: Vec, + /// deprecated, use field `tagline` or /api/v3/tagline/list + pub taglines: Vec<()>, + /// deprecated, use /api/v3/custom_emoji/list + pub custom_emojis: Vec<()>, + /// If the site has any taglines, a random one is included here for displaying + pub tagline: Option, + /// A list of external auth methods your site supports. + pub oauth_providers: Option>, + pub admin_oauth_providers: Option>, pub blocked_urls: Vec, } diff --git a/crates/api_common/src/tagline.rs b/crates/api_common/src/tagline.rs new file mode 100644 index 000000000..3090a2678 --- /dev/null +++ b/crates/api_common/src/tagline.rs @@ -0,0 +1,55 @@ +use lemmy_db_schema::{newtypes::TaglineId, source::tagline::Tagline}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Create a tagline +pub struct CreateTagline { + pub content: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Update a tagline +pub struct UpdateTagline { + pub id: TaglineId, + pub content: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Delete a tagline +pub struct DeleteTagline { + pub id: TaglineId, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct TaglineResponse { + pub tagline: Tagline, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A response for taglines. +pub struct ListTaglinesResponse { + pub taglines: Vec, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Fetches a list of taglines. +pub struct ListTaglines { + pub page: Option, + pub limit: Option, +} diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 0b8e56273..87cdf9eef 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -11,30 +11,35 @@ 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}, + newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId}, source::{ - comment::{Comment, CommentUpdateForm}, + comment::{Comment, CommentLike, CommentUpdateForm}, community::{Community, CommunityModerator, CommunityUpdateForm}, community_block::CommunityBlock, email_verification::{EmailVerification, EmailVerificationForm}, - images::RemoteImage, + images::{ImageDetails, RemoteImage}, instance::Instance, instance_block::InstanceBlock, local_site::LocalSite, local_site_rate_limit::LocalSiteRateLimit, local_site_url_blocklist::LocalSiteUrlBlocklist, + moderator::{ModRemoveComment, ModRemoveCommentForm, ModRemovePost, ModRemovePostForm}, + oauth_account::OAuthAccount, password_reset_request::PasswordResetRequest, person::{Person, PersonUpdateForm}, person_block::PersonBlock, - post::{Post, PostRead}, + post::{Post, PostLike, PostRead}, + registration_application::RegistrationApplication, site::Site, }, - traits::Crud, + traits::{Crud, Likeable}, utils::DbPool, + FederationMode, + RegistrationMode, }; use lemmy_db_views::{ comment_view::CommentQuery, - structs::{LocalImageView, LocalUserView}, + structs::{LocalImageView, LocalUserView, SiteView}, }; use lemmy_db_views_actor::structs::{ CommunityModeratorView, @@ -45,10 +50,14 @@ use lemmy_utils::{ email::{send_email, translations::Lang}, error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, rate_limit::{ActionType, BucketConfig}, - settings::structs::{PictrsImageMode, Settings}, + settings::{ + structs::{PictrsImageMode, Settings}, + SETTINGS, + }, utils::{ - markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links}, + markdown::{image_links::markdown_rewrite_image_links, markdown_check_for_blocked_urls}, slurs::{build_slur_regex, remove_slurs}, + validation::clean_urls_in_text, }, CACHE_DURATION_FEDERATION, }; @@ -69,13 +78,7 @@ pub async fn is_mod_or_admin( community_id: CommunityId, ) -> LemmyResult<()> { check_user_valid(person)?; - - let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?; - if !is_mod_or_admin { - Err(LemmyErrorType::NotAModOrAdmin)? - } else { - Ok(()) - } + CommunityView::check_is_mod_or_admin(pool, person.id, community_id).await } #[tracing::instrument(skip_all)] @@ -106,13 +109,7 @@ pub async fn check_community_mod_of_any_or_admin_action( let person = &local_user_view.person; check_user_valid(person)?; - - let is_mod_of_any_or_admin = CommunityView::is_mod_of_any_or_admin(pool, person.id).await?; - if !is_mod_of_any_or_admin { - Err(LemmyErrorType::NotAModOrAdmin)? - } else { - Ok(()) - } + CommunityView::check_is_mod_of_any_or_admin(pool, person.id).await } pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> { @@ -171,9 +168,7 @@ pub async fn update_read_comments( ..PersonPostAggregatesForm::default() }; - PersonPostAggregates::upsert(pool, &person_post_agg_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; + PersonPostAggregates::upsert(pool, &person_post_agg_form).await?; Ok(()) } @@ -191,6 +186,44 @@ pub fn check_user_valid(person: &Person) -> LemmyResult<()> { } } +/// Check if the user's email is verified if email verification is turned on +/// However, skip checking verification if the user is an admin +pub fn check_email_verified( + local_user_view: &LocalUserView, + site_view: &SiteView, +) -> LemmyResult<()> { + if !local_user_view.local_user.admin + && site_view.local_site.require_email_verification + && !local_user_view.local_user.email_verified + { + Err(LemmyErrorType::EmailNotVerified)? + } + Ok(()) +} + +pub async fn check_registration_application( + local_user_view: &LocalUserView, + local_site: &LocalSite, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + if (local_site.registration_mode == RegistrationMode::RequireApplication + || local_site.registration_mode == RegistrationMode::Closed) + && !local_user_view.local_user.accepted_application + && !local_user_view.local_user.admin + { + // Fetch the registration application. If no admin id is present its still pending. Otherwise it + // was processed (either accepted or denied). + let local_user_id = local_user_view.local_user.id; + let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?; + if registration.admin_id.is_some() { + Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))? + } else { + Err(LemmyErrorType::RegistrationApplicationIsPending)? + } + } + Ok(()) +} + /// Checks that a normal user action (eg posting or voting) is allowed in a given community. /// /// In particular it checks that neither the user nor community are banned or deleted, and that @@ -202,7 +235,7 @@ pub async fn check_community_user_action( ) -> LemmyResult<()> { check_user_valid(person)?; check_community_deleted_removed(community_id, pool).await?; - check_community_ban(person, community_id, pool).await?; + CommunityPersonBanView::check(pool, person.id, community_id).await?; Ok(()) } @@ -210,28 +243,13 @@ async fn check_community_deleted_removed( community_id: CommunityId, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { - let community = Community::read(pool, community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(pool, community_id).await?; if community.deleted || community.removed { Err(LemmyErrorType::Deleted)? } Ok(()) } -async fn check_community_ban( - person: &Person, - community_id: CommunityId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - // check if user was banned from site or community - let is_banned = CommunityPersonBanView::get(pool, person.id, community_id).await?; - if is_banned { - Err(LemmyErrorType::BannedFromCommunity)? - } - Ok(()) -} - /// Check that the given user can perform a mod action in the community. /// /// In particular it checks that he is an admin or mod, wasn't banned and the community isn't @@ -243,7 +261,7 @@ pub async fn check_community_mod_action( pool: &mut DbPool<'_>, ) -> LemmyResult<()> { is_mod_or_admin(pool, person, community_id).await?; - check_community_ban(person, community_id, pool).await?; + CommunityPersonBanView::check(pool, person.id, community_id).await?; // it must be possible to restore deleted community if !allow_deleted { @@ -269,51 +287,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> { } } -/// Throws an error if a recipient has blocked a person. -#[tracing::instrument(skip_all)] -pub async fn check_person_block( - my_id: PersonId, - potential_blocker_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?; - if is_blocked { - Err(LemmyErrorType::PersonIsBlocked)? - } else { - Ok(()) - } -} - -/// Throws an error if a recipient has blocked a community. -#[tracing::instrument(skip_all)] -async fn check_community_block( - community_id: CommunityId, - person_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?; - if is_blocked { - Err(LemmyErrorType::CommunityIsBlocked)? - } else { - Ok(()) - } -} - -/// Throws an error if a recipient has blocked an instance. -#[tracing::instrument(skip_all)] -async fn check_instance_block( - instance_id: InstanceId, - person_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?; - if is_blocked { - Err(LemmyErrorType::InstanceIsBlocked)? - } else { - Ok(()) - } -} - #[tracing::instrument(skip_all)] pub async fn check_person_instance_community_block( my_id: PersonId, @@ -322,19 +295,42 @@ pub async fn check_person_instance_community_block( community_id: CommunityId, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { - check_person_block(my_id, potential_blocker_id, pool).await?; - check_instance_block(community_instance_id, potential_blocker_id, pool).await?; - check_community_block(community_id, potential_blocker_id, pool).await?; + PersonBlock::read(pool, potential_blocker_id, my_id).await?; + InstanceBlock::read(pool, potential_blocker_id, community_instance_id).await?; + CommunityBlock::read(pool, potential_blocker_id, community_id).await?; Ok(()) } +/// A vote item type used to check the vote mode. +pub enum VoteItem { + Post(PostId), + Comment(CommentId), +} + #[tracing::instrument(skip_all)] -pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> { - if score == -1 && !local_site.enable_downvotes { - Err(LemmyErrorType::DownvotesAreDisabled)? - } else { - Ok(()) +pub async fn check_local_vote_mode( + score: i16, + vote_item: VoteItem, + local_site: &LocalSite, + person_id: PersonId, + pool: &mut DbPool<'_>, +) -> LemmyResult<()> { + let (downvote_setting, upvote_setting) = match vote_item { + VoteItem::Post(_) => (local_site.post_downvotes, local_site.post_upvotes), + VoteItem::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes), + }; + + let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable; + let upvote_fail = score == 1 && upvote_setting == FederationMode::Disable; + + // Undo previous vote for item if new vote fails + if downvote_fail || upvote_fail { + match vote_item { + VoteItem::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?, + VoteItem::Comment(comment_id) => CommentLike::remove(pool, person_id, comment_id).await?, + }; } + Ok(()) } /// Dont allow bots to do certain actions, like voting @@ -537,13 +533,6 @@ pub fn local_site_opt_to_slur_regex(local_site: &Option) -> Option) -> bool { - local_site - .as_ref() - .map(|site| site.enable_nsfw) - .unwrap_or(false) -} - pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult { static URL_BLOCKLIST: LazyLock> = LazyLock::new(|| { Cache::builder() @@ -667,7 +656,7 @@ pub async fn purge_image_posts_for_person( /// Delete a local_user's images async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> { - if let Ok(Some(local_user)) = LocalUserView::read_person(&mut context.pool(), person_id).await { + if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await { let pictrs_uploads = LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id) .await?; @@ -706,106 +695,189 @@ pub async fn purge_image_posts_for_community( Ok(()) } -pub async fn remove_user_data( +/// Removes or restores user data. +pub async fn remove_or_restore_user_data( + mod_person_id: PersonId, banned_person_id: PersonId, + removed: bool, + reason: &Option, context: &LemmyContext, ) -> LemmyResult<()> { let pool = &mut context.pool(); - // Purge user images - let person = Person::read(pool, banned_person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; - if let Some(avatar) = person.avatar { - purge_image_from_pictrs(&avatar, context).await.ok(); - } - if let Some(banner) = person.banner { - purge_image_from_pictrs(&banner, context).await.ok(); + + // Only these actions are possible when removing, not restoring + if removed { + // Purge user images + let person = Person::read(pool, banned_person_id).await?; + if let Some(avatar) = person.avatar { + purge_image_from_pictrs(&avatar, context).await.ok(); + } + if let Some(banner) = person.banner { + purge_image_from_pictrs(&banner, context).await.ok(); + } + + // Update the fields to None + Person::update( + pool, + banned_person_id, + &PersonUpdateForm { + avatar: Some(None), + banner: Some(None), + bio: Some(None), + ..Default::default() + }, + ) + .await?; + + // Purge image posts + purge_image_posts_for_person(banned_person_id, context).await?; + + // Communities + // Remove all communities where they're the top mod + // for now, remove the communities manually + let first_mod_communities = CommunityModeratorView::get_community_first_mods(pool).await?; + + // Filter to only this banned users top communities + let banned_user_first_communities: Vec = first_mod_communities + .into_iter() + .filter(|fmc| fmc.moderator.id == banned_person_id) + .collect(); + + for first_mod_community in banned_user_first_communities { + let community_id = first_mod_community.community.id; + Community::update( + pool, + community_id, + &CommunityUpdateForm { + removed: Some(removed), + ..Default::default() + }, + ) + .await?; + + // Delete the community images + if let Some(icon) = first_mod_community.community.icon { + purge_image_from_pictrs(&icon, context).await.ok(); + } + if let Some(banner) = first_mod_community.community.banner { + purge_image_from_pictrs(&banner, context).await.ok(); + } + // Update the fields to None + Community::update( + pool, + community_id, + &CommunityUpdateForm { + icon: Some(None), + banner: Some(None), + ..Default::default() + }, + ) + .await?; + } } - // Update the fields to None - Person::update( + // Posts + let removed_or_restored_posts = + Post::update_removed_for_creator(pool, banned_person_id, None, removed).await?; + create_modlog_entries_for_removed_or_restored_posts( pool, - banned_person_id, - &PersonUpdateForm { - avatar: Some(None), - banner: Some(None), - bio: Some(None), - ..Default::default() - }, + mod_person_id, + removed_or_restored_posts.iter().map(|r| r.id).collect(), + removed, + reason, ) .await?; - // Posts - Post::update_removed_for_creator(pool, banned_person_id, None, true).await?; - - // Purge image posts - purge_image_posts_for_person(banned_person_id, context).await?; - - // Communities - // Remove all communities where they're the top mod - // for now, remove the communities manually - let first_mod_communities = CommunityModeratorView::get_community_first_mods(pool).await?; - - // Filter to only this banned users top communities - let banned_user_first_communities: Vec = first_mod_communities - .into_iter() - .filter(|fmc| fmc.moderator.id == banned_person_id) - .collect(); - - for first_mod_community in banned_user_first_communities { - let community_id = first_mod_community.community.id; - Community::update( - pool, - community_id, - &CommunityUpdateForm { - removed: Some(true), - ..Default::default() - }, - ) - .await?; - - // Delete the community images - if let Some(icon) = first_mod_community.community.icon { - purge_image_from_pictrs(&icon, context).await.ok(); - } - if let Some(banner) = first_mod_community.community.banner { - purge_image_from_pictrs(&banner, context).await.ok(); - } - // Update the fields to None - Community::update( - pool, - community_id, - &CommunityUpdateForm { - icon: Some(None), - banner: Some(None), - ..Default::default() - }, - ) - .await?; - } - // Comments - Comment::update_removed_for_creator(pool, banned_person_id, true).await?; + let removed_or_restored_comments = + Comment::update_removed_for_creator(pool, banned_person_id, removed).await?; + create_modlog_entries_for_removed_or_restored_comments( + pool, + mod_person_id, + removed_or_restored_comments.iter().map(|r| r.id).collect(), + removed, + reason, + ) + .await?; Ok(()) } -pub async fn remove_user_data_in_community( +async fn create_modlog_entries_for_removed_or_restored_posts( + pool: &mut DbPool<'_>, + mod_person_id: PersonId, + post_ids: Vec, + removed: bool, + reason: &Option, +) -> LemmyResult<()> { + // Build the forms + let forms = post_ids + .iter() + .map(|&post_id| ModRemovePostForm { + mod_person_id, + post_id, + removed: Some(removed), + reason: reason.clone(), + }) + .collect(); + + ModRemovePost::create_multiple(pool, &forms).await?; + + Ok(()) +} + +async fn create_modlog_entries_for_removed_or_restored_comments( + pool: &mut DbPool<'_>, + mod_person_id: PersonId, + comment_ids: Vec, + removed: bool, + reason: &Option, +) -> LemmyResult<()> { + // Build the forms + let forms = comment_ids + .iter() + .map(|&comment_id| ModRemoveCommentForm { + mod_person_id, + comment_id, + removed: Some(removed), + reason: reason.clone(), + }) + .collect(); + + ModRemoveComment::create_multiple(pool, &forms).await?; + + Ok(()) +} + +pub async fn remove_or_restore_user_data_in_community( community_id: CommunityId, + mod_person_id: PersonId, banned_person_id: PersonId, + remove: bool, + reason: &Option, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { // Posts - Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?; + let posts = + Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), remove).await?; + create_modlog_entries_for_removed_or_restored_posts( + pool, + mod_person_id, + posts.iter().map(|r| r.id).collect(), + remove, + reason, + ) + .await?; // Comments // TODO Diesel doesn't allow updates with joins, so this has to be a loop + let site = Site::read_local(pool).await?; let comments = CommentQuery { creator_id: Some(banned_person_id), community_id: Some(community_id), ..Default::default() } - .list(pool) + .list(&site, pool) .await?; for comment_view in &comments { @@ -814,22 +886,29 @@ pub async fn remove_user_data_in_community( pool, comment_id, &CommentUpdateForm { - removed: Some(true), + removed: Some(remove), ..Default::default() }, ) .await?; } + create_modlog_entries_for_removed_or_restored_comments( + pool, + mod_person_id, + comments.iter().map(|r| r.comment.id).collect(), + remove, + reason, + ) + .await?; + Ok(()) } pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> { let pool = &mut context.pool(); - let person = Person::read(pool, person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person = Person::read(pool, person_id).await?; // Delete their local images, if they're a local user delete_local_user_images(person_id, context).await.ok(); @@ -858,6 +937,11 @@ pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> // Leave communities they mod CommunityModerator::leave_all_communities(pool, person_id).await?; + // Delete the oauth accounts linked to the local user + if let Ok(local_user) = LocalUserView::read_person(pool, person_id).await { + OAuthAccount::delete_user_accounts(pool, local_user.local_user.id).await?; + } + Person::delete_account(pool, person_id).await?; Ok(()) @@ -892,12 +976,8 @@ pub fn generate_followers_url(actor_id: &DbUrl) -> Result { Ok(Url::parse(&format!("{actor_id}/followers"))?.into()) } -pub fn generate_inbox_url(actor_id: &DbUrl) -> Result { - Ok(Url::parse(&format!("{actor_id}/inbox"))?.into()) -} - -pub fn generate_shared_inbox_url(settings: &Settings) -> LemmyResult { - let url = format!("{}/inbox", settings.get_protocol_and_hostname()); +pub fn generate_inbox_url() -> LemmyResult { + let url = format!("{}/inbox", SETTINGS.get_protocol_and_hostname()); Ok(Url::parse(&url)?.into()) } @@ -940,6 +1020,18 @@ fn limit_expire_time(expires: DateTime) -> LemmyResult } } +#[tracing::instrument(skip_all)] +pub fn check_conflicting_like_filters( + liked_only: Option, + disliked_only: Option, +) -> LemmyResult<()> { + if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() { + Err(LemmyErrorType::ContradictingFilters)? + } else { + Ok(()) + } +} + pub async fn process_markdown( text: &str, slur_regex: &Option, @@ -947,11 +1039,13 @@ pub async fn process_markdown( context: &LemmyContext, ) -> LemmyResult { let text = remove_slurs(text, slur_regex); + let text = clean_urls_in_text(&text); markdown_check_for_blocked_urls(&text, url_blocklist)?; if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages { let (text, links) = markdown_rewrite_image_links(text); + RemoteImage::create(&mut context.pool(), links.clone()).await?; // Create images and image detail rows for link in links { @@ -961,7 +1055,7 @@ pub async fn process_markdown( let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?; let details_form = details.build_image_details_form(&proxied); - RemoteImage::create(&mut context.pool(), &details_form).await?; + ImageDetails::create(&mut context.pool(), &details_form).await?; } } Ok(text) @@ -997,13 +1091,15 @@ async fn proxy_image_link_internal( if link.domain() == Some(&context.settings().hostname) { Ok(link.into()) } else if image_mode == PictrsImageMode::ProxyAllImages { + RemoteImage::create(&mut context.pool(), vec![link.clone()]).await?; + 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?; + ImageDetails::create(&mut context.pool(), &details_form).await?; }; Ok(proxied.into()) @@ -1071,11 +1167,20 @@ fn build_proxied_image_url( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; + use lemmy_db_schema::source::{ + comment::CommentInsertForm, + community::CommunityInsertForm, + person::PersonInsertForm, + post::PostInsertForm, + }; + use lemmy_db_views_moderator::structs::{ + ModRemoveCommentView, + ModRemovePostView, + ModlogListParams, + }; use pretty_assertions::assert_eq; use serial_test::serial; @@ -1097,48 +1202,42 @@ mod tests { } #[test] - fn test_limit_ban_term() { + fn test_limit_ban_term() -> LemmyResult<()> { // Ban expires in past, should throw error assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err()); // Legitimate ban term, return same value let fourteen_days = Utc::now() + Days::new(14); - assert_eq!( - limit_expire_time(fourteen_days).unwrap(), - Some(fourteen_days) - ); + assert_eq!(limit_expire_time(fourteen_days)?, Some(fourteen_days)); let nine_years = Utc::now() + Days::new(365 * 9); - assert_eq!(limit_expire_time(nine_years).unwrap(), Some(nine_years)); + assert_eq!(limit_expire_time(nine_years)?, Some(nine_years)); // Too long ban term, changes to None (permanent ban) - assert_eq!( - limit_expire_time(Utc::now() + Days::new(365 * 11)).unwrap(), - None - ); + assert_eq!(limit_expire_time(Utc::now() + Days::new(365 * 11))?, None); + + Ok(()) } #[tokio::test] #[serial] - async fn test_proxy_image_link() { + async fn test_proxy_image_link() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; // image from local domain is unchanged - let local_url = Url::parse("http://lemmy-alpha/image.png").unwrap(); + let local_url = Url::parse("http://lemmy-alpha/image.png")?; let proxied = proxy_image_link_internal(local_url.clone(), PictrsImageMode::ProxyAllImages, &context) - .await - .unwrap(); + .await?; assert_eq!(&local_url, proxied.inner()); // image from remote domain is proxied - let remote_image = Url::parse("http://lemmy-beta/image.png").unwrap(); + let remote_image = Url::parse("http://lemmy-beta/image.png")?; let proxied = proxy_image_link_internal( remote_image.clone(), PictrsImageMode::ProxyAllImages, &context, ) - .await - .unwrap(); + .await?; assert_eq!( "https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png", proxied.as_str() @@ -1149,7 +1248,161 @@ mod tests { assert!( RemoteImage::validate(&mut context.pool(), remote_image.into()) .await - .is_err() + .is_ok() ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn test_mod_remove_or_restore_data() -> LemmyResult<()> { + let context = LemmyContext::init_test_context().await; + let pool = &mut context.pool(); + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + + let new_mod = PersonInsertForm::test_form(inserted_instance.id, "modder"); + let inserted_mod = Person::create(pool, &new_mod).await?; + + let new_person = PersonInsertForm::test_form(inserted_instance.id, "chrimbus"); + let inserted_person = Person::create(pool, &new_person).await?; + + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "mod_community crepes".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; + + let post_form_1 = PostInsertForm::new( + "A test post tubular".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post_1 = Post::create(pool, &post_form_1).await?; + + let post_form_2 = PostInsertForm::new( + "A test post radical".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post_2 = Post::create(pool, &post_form_2).await?; + + let comment_form_1 = CommentInsertForm::new( + inserted_person.id, + inserted_post_1.id, + "A test comment tubular".into(), + ); + let _inserted_comment_1 = Comment::create(pool, &comment_form_1, None).await?; + + let comment_form_2 = CommentInsertForm::new( + inserted_person.id, + inserted_post_2.id, + "A test comment radical".into(), + ); + let _inserted_comment_2 = Comment::create(pool, &comment_form_2, None).await?; + + // Remove the user data + remove_or_restore_user_data( + inserted_mod.id, + inserted_person.id, + true, + &Some("a remove reason".to_string()), + &context, + ) + .await?; + + // Verify that their posts and comments are removed. + let params = ModlogListParams { + community_id: None, + mod_person_id: None, + other_person_id: None, + post_id: None, + comment_id: None, + page: None, + limit: None, + hide_modlog_names: false, + }; + + // Posts + let post_modlog = ModRemovePostView::list(pool, params).await?; + assert_eq!(2, post_modlog.len()); + + let mod_removed_posts = post_modlog + .iter() + .map(|p| p.mod_remove_post.removed) + .collect::>(); + assert_eq!(vec![true, true], mod_removed_posts); + + let removed_posts = post_modlog + .iter() + .map(|p| p.post.removed) + .collect::>(); + assert_eq!(vec![true, true], removed_posts); + + // Comments + let comment_modlog = ModRemoveCommentView::list(pool, params).await?; + assert_eq!(2, comment_modlog.len()); + + let mod_removed_comments = comment_modlog + .iter() + .map(|p| p.mod_remove_comment.removed) + .collect::>(); + assert_eq!(vec![true, true], mod_removed_comments); + + let removed_comments = comment_modlog + .iter() + .map(|p| p.comment.removed) + .collect::>(); + assert_eq!(vec![true, true], removed_comments); + + // Now restore the content, and make sure it got appended + remove_or_restore_user_data( + inserted_mod.id, + inserted_person.id, + false, + &Some("a restore reason".to_string()), + &context, + ) + .await?; + + // Posts + let post_modlog = ModRemovePostView::list(pool, params).await?; + assert_eq!(4, post_modlog.len()); + + let mod_restored_posts = post_modlog + .iter() + .map(|p| p.mod_remove_post.removed) + .collect::>(); + assert_eq!(vec![false, false, true, true], mod_restored_posts); + + let restored_posts = post_modlog + .iter() + .map(|p| p.post.removed) + .collect::>(); + // All of these will be false, cause its the current state of the post + assert_eq!(vec![false, false, false, false], restored_posts); + + // Comments + let comment_modlog = ModRemoveCommentView::list(pool, params).await?; + assert_eq!(4, comment_modlog.len()); + + let mod_restored_comments = comment_modlog + .iter() + .map(|p| p.mod_remove_comment.removed) + .collect::>(); + assert_eq!(vec![false, false, true, true], mod_restored_comments); + + let restored_comments = comment_modlog + .iter() + .map(|p| p.comment.removed) + .collect::>(); + assert_eq!(vec![false, false, false, false], restored_comments); + + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml index 6055f9ef0..2793beac3 100644 --- a/crates/api_crud/Cargo.toml +++ b/crates/api_crud/Cargo.toml @@ -27,8 +27,12 @@ futures.workspace = true uuid = { workspace = true } moka.workspace = true anyhow.workspace = true -webmention = "0.5.0" +chrono.workspace = true +webmention = "0.6.0" accept-language = "3.1.0" +serde_json = { workspace = true } +serde = { workspace = true } +serde_with = { workspace = true } [package.metadata.cargo-machete] ignored = ["futures"] diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 49de9d5de..2f67fa7e7 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -30,10 +30,9 @@ 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}, + MAX_COMMENT_DEPTH_LIMIT, }; -const MAX_COMMENT_DEPTH_LIMIT: usize = 100; - #[tracing::instrument(skip(context))] pub async fn create_comment( data: Json, @@ -57,8 +56,7 @@ pub async fn create_comment( Some(&local_user_view.local_user), true, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; let post = post_view.post; let community_id = post_view.community.id; @@ -79,8 +77,7 @@ pub async fn create_comment( Comment::read(&mut context.pool(), parent_id).await.ok() } else { None - } - .flatten(); + }; // If there's a parent_id, check to make sure that comment is in that post // Strange issue where sometimes the post ID of the parent comment is incorrect @@ -91,16 +88,9 @@ pub async fn create_comment( check_comment_depth(parent)?; } - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - data.language_id, - community_id, - ) - .await?; - // attempt to set default language if none was provided let language_id = match data.language_id { - Some(lid) => Some(lid), + Some(lid) => lid, None => { default_post_language( &mut context.pool(), @@ -111,12 +101,13 @@ pub async fn create_comment( } }; - let comment_form = CommentInsertForm::builder() - .content(content.clone()) - .post_id(data.post_id) - .creator_id(local_user_view.person.id) - .language_id(language_id) - .build(); + CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id) + .await?; + + let comment_form = CommentInsertForm { + language_id: Some(language_id), + ..CommentInsertForm::new(local_user_view.person.id, data.post_id, content.clone()) + }; // Create the comment let parent_path = parent_opt.clone().map(|t| t.path); @@ -141,7 +132,6 @@ pub async fn create_comment( // You like your own comment by default let like_form = CommentLikeForm { comment_id: inserted_comment.id, - post_id: post.id, person_id: local_user_view.person.id, score: 1, }; diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 8c81608c8..2b5f35827 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -26,8 +26,7 @@ pub async fn delete_comment( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; // Dont delete it if its already been deleted. if orig_comment.comment.deleted == data.deleted { diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index ec4923e93..3c137a984 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -31,8 +31,7 @@ pub async fn remove_comment( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index ed9460825..51f65aa67 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -41,8 +41,7 @@ pub async fn update_comment( comment_id, Some(&local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; check_community_user_action( &local_user_view.person, @@ -56,13 +55,14 @@ pub async fn update_comment( Err(LemmyErrorType::NoCommentEditAllowed)? } - let language_id = data.language_id; - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - language_id, - orig_comment.community.id, - ) - .await?; + if let Some(language_id) = data.language_id { + CommunityLanguage::is_allowed_community_language( + &mut context.pool(), + language_id, + orig_comment.community.id, + ) + .await?; + } let slur_regex = local_site_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(&context).await?; diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 4289b7d24..f02d733e5 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -8,7 +8,6 @@ use lemmy_api_common::{ generate_followers_url, generate_inbox_url, generate_local_apub_endpoint, - generate_shared_inbox_url, get_url_blocklist, is_admin, local_site_to_slur_regex, @@ -47,9 +46,7 @@ pub async fn create_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let local_site = site_view.local_site; if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() { @@ -90,23 +87,24 @@ pub async fn create_community( // When you create a community, make sure the user becomes a moderator and a follower let keypair = generate_actor_keypair()?; - let community_form = CommunityInsertForm::builder() - .name(data.name.clone()) - .title(data.title.clone()) - .description(description) - .icon(icon) - .banner(banner) - .nsfw(data.nsfw) - .actor_id(Some(community_actor_id.clone())) - .private_key(Some(keypair.private_key)) - .public_key(keypair.public_key) - .followers_url(Some(generate_followers_url(&community_actor_id)?)) - .inbox_url(Some(generate_inbox_url(&community_actor_id)?)) - .shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?)) - .posting_restricted_to_mods(data.posting_restricted_to_mods) - .instance_id(site_view.site.instance_id) - .visibility(data.visibility) - .build(); + let community_form = CommunityInsertForm { + description, + icon, + banner, + nsfw: data.nsfw, + actor_id: Some(community_actor_id.clone()), + private_key: Some(keypair.private_key), + followers_url: Some(generate_followers_url(&community_actor_id)?), + inbox_url: Some(generate_inbox_url()?), + posting_restricted_to_mods: data.posting_restricted_to_mods, + visibility: data.visibility, + ..CommunityInsertForm::new( + site_view.site.instance_id, + data.name.clone(), + data.title.clone(), + keypair.public_key, + ) + }; let inserted_community = Community::create(&mut context.pool(), &community_form) .await diff --git a/crates/api_crud/src/community/list.rs b/crates/api_crud/src/community/list.rs index 587b5cdfa..9c13ae89f 100644 --- a/crates/api_crud/src/community/list.rs +++ b/crates/api_crud/src/community/list.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::community_view::CommunityQuery; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_communities( @@ -14,9 +14,7 @@ pub async fn list_communities( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let local_site = SiteView::read_local(&mut context.pool()).await?; let is_admin = local_user_view .as_ref() .map(|luv| is_admin(luv).is_ok()) diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 6190a0ca7..798f4f4c1 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -51,9 +51,7 @@ pub async fn update_community( is_valid_body_field(desc, false)?; } - let old_community = Community::read(&mut context.pool(), data.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let old_community = Community::read(&mut context.pool(), data.community_id).await?; let icon = diesel_url_update(data.icon.as_deref())?; replace_image(&icon, &old_community.icon, &context).await?; diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs index 3c5ce3296..333a7ce89 100644 --- a/crates/api_crud/src/custom_emoji/create.rs +++ b/crates/api_crud/src/custom_emoji/create.rs @@ -5,10 +5,12 @@ use lemmy_api_common::{ custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, utils::is_admin, }; -use lemmy_db_schema::source::{ - custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, - custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, - local_site::LocalSite, +use lemmy_db_schema::{ + source::{ + custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, + custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, + }, + traits::Crud, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_utils::error::LemmyResult; @@ -19,24 +21,20 @@ pub async fn create_custom_emoji( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; - let emoji_form = CustomEmojiInsertForm::builder() - .local_site_id(local_site.id) - .shortcode(data.shortcode.to_lowercase().trim().to_string()) - .alt_text(data.alt_text.to_string()) - .category(data.category.to_string()) - .image_url(data.clone().image_url.into()) - .build(); + let emoji_form = CustomEmojiInsertForm::new( + data.shortcode.to_lowercase().trim().to_string(), + data.clone().image_url.into(), + data.alt_text.to_string(), + data.category.to_string(), + ); let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; let mut keywords = vec![]; for keyword in &data.keywords { - let keyword_form = CustomEmojiKeywordInsertForm::builder() - .custom_emoji_id(emoji.id) - .keyword(keyword.to_lowercase().trim().to_string()) - .build(); + let keyword_form = + CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string()); keywords.push(keyword_form); } CustomEmojiKeyword::create(&mut context.pool(), keywords).await?; diff --git a/crates/api_crud/src/custom_emoji/delete.rs b/crates/api_crud/src/custom_emoji/delete.rs index 45ac8d0ba..818fd4d88 100644 --- a/crates/api_crud/src/custom_emoji/delete.rs +++ b/crates/api_crud/src/custom_emoji/delete.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ utils::is_admin, SuccessResponse, }; -use lemmy_db_schema::source::custom_emoji::CustomEmoji; +use lemmy_db_schema::{source::custom_emoji::CustomEmoji, traits::Crud}; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyResult; diff --git a/crates/api_crud/src/custom_emoji/list.rs b/crates/api_crud/src/custom_emoji/list.rs new file mode 100644 index 000000000..6ee5a44b0 --- /dev/null +++ b/crates/api_crud/src/custom_emoji/list.rs @@ -0,0 +1,25 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + custom_emoji::{ListCustomEmojis, ListCustomEmojisResponse}, +}; +use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn list_custom_emojis( + data: Query, + local_user_view: Option, + context: Data, +) -> Result, LemmyError> { + let custom_emojis = CustomEmojiView::list( + &mut context.pool(), + &data.category, + data.page, + data.limit, + data.ignore_page_limits.unwrap_or(false), + ) + .await?; + + Ok(Json(ListCustomEmojisResponse { custom_emojis })) +} diff --git a/crates/api_crud/src/custom_emoji/mod.rs b/crates/api_crud/src/custom_emoji/mod.rs index fdb2f5561..ffd48daf6 100644 --- a/crates/api_crud/src/custom_emoji/mod.rs +++ b/crates/api_crud/src/custom_emoji/mod.rs @@ -1,3 +1,4 @@ pub mod create; pub mod delete; +pub mod list; pub mod update; diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs index 63246f85d..6087f6969 100644 --- a/crates/api_crud/src/custom_emoji/update.rs +++ b/crates/api_crud/src/custom_emoji/update.rs @@ -5,10 +5,12 @@ use lemmy_api_common::{ custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, utils::is_admin, }; -use lemmy_db_schema::source::{ - custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, - custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, - local_site::LocalSite, +use lemmy_db_schema::{ + source::{ + custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, + custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, + }, + traits::Crud, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_utils::error::LemmyResult; @@ -19,24 +21,20 @@ pub async fn update_custom_emoji( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; - let emoji_form = CustomEmojiUpdateForm::builder() - .local_site_id(local_site.id) - .alt_text(data.alt_text.to_string()) - .category(data.category.to_string()) - .image_url(data.clone().image_url.into()) - .build(); + let emoji_form = CustomEmojiUpdateForm::new( + data.clone().image_url.into(), + data.alt_text.to_string(), + data.category.to_string(), + ); let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?; let mut keywords = vec![]; for keyword in &data.keywords { - let keyword_form = CustomEmojiKeywordInsertForm::builder() - .custom_emoji_id(emoji.id) - .keyword(keyword.to_lowercase().trim().to_string()) - .build(); + let keyword_form = + CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string()); keywords.push(keyword_form); } CustomEmojiKeyword::create(&mut context.pool(), keywords).await?; diff --git a/crates/api_crud/src/lib.rs b/crates/api_crud/src/lib.rs index aee3e8134..7d1b901b9 100644 --- a/crates/api_crud/src/lib.rs +++ b/crates/api_crud/src/lib.rs @@ -1,7 +1,9 @@ pub mod comment; pub mod community; pub mod custom_emoji; +pub mod oauth_provider; pub mod post; pub mod private_message; pub mod site; +pub mod tagline; pub mod user; diff --git a/crates/api_crud/src/oauth_provider/create.rs b/crates/api_crud/src/oauth_provider/create.rs new file mode 100644 index 000000000..fe44ae56e --- /dev/null +++ b/crates/api_crud/src/oauth_provider/create.rs @@ -0,0 +1,42 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + oauth_provider::CreateOAuthProvider, + utils::is_admin, +}; +use lemmy_db_schema::{ + source::oauth_provider::{OAuthProvider, OAuthProviderInsertForm}, + traits::Crud, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; +use url::Url; + +#[tracing::instrument(skip(context))] +pub async fn create_oauth_provider( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let cloned_data = data.clone(); + let oauth_provider_form = OAuthProviderInsertForm { + display_name: cloned_data.display_name, + issuer: Url::parse(&cloned_data.issuer)?.into(), + authorization_endpoint: Url::parse(&cloned_data.authorization_endpoint)?.into(), + token_endpoint: Url::parse(&cloned_data.token_endpoint)?.into(), + userinfo_endpoint: Url::parse(&cloned_data.userinfo_endpoint)?.into(), + id_claim: cloned_data.id_claim, + client_id: data.client_id.to_string(), + client_secret: data.client_secret.to_string(), + scopes: data.scopes.to_string(), + auto_verify_email: data.auto_verify_email, + account_linking_enabled: data.account_linking_enabled, + enabled: data.enabled, + }; + let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?; + Ok(Json(oauth_provider)) +} diff --git a/crates/api_crud/src/oauth_provider/delete.rs b/crates/api_crud/src/oauth_provider/delete.rs new file mode 100644 index 000000000..0d4d616cc --- /dev/null +++ b/crates/api_crud/src/oauth_provider/delete.rs @@ -0,0 +1,25 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + oauth_provider::DeleteOAuthProvider, + utils::is_admin, + SuccessResponse, +}; +use lemmy_db_schema::{source::oauth_provider::OAuthProvider, traits::Crud}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; + +#[tracing::instrument(skip(context))] +pub async fn delete_oauth_provider( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + OAuthProvider::delete(&mut context.pool(), data.id) + .await + .with_lemmy_type(LemmyErrorType::CouldntDeleteOauthProvider)?; + Ok(Json(SuccessResponse::default())) +} diff --git a/crates/api_crud/src/oauth_provider/mod.rs b/crates/api_crud/src/oauth_provider/mod.rs new file mode 100644 index 000000000..fdb2f5561 --- /dev/null +++ b/crates/api_crud/src/oauth_provider/mod.rs @@ -0,0 +1,3 @@ +pub mod create; +pub mod delete; +pub mod update; diff --git a/crates/api_crud/src/oauth_provider/update.rs b/crates/api_crud/src/oauth_provider/update.rs new file mode 100644 index 000000000..b4734bf36 --- /dev/null +++ b/crates/api_crud/src/oauth_provider/update.rs @@ -0,0 +1,42 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{context::LemmyContext, oauth_provider::EditOAuthProvider, utils::is_admin}; +use lemmy_db_schema::{ + source::oauth_provider::{OAuthProvider, OAuthProviderUpdateForm}, + traits::Crud, + utils::{diesel_required_string_update, diesel_required_url_update, naive_now}, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn update_oauth_provider( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let cloned_data = data.clone(); + let oauth_provider_form = OAuthProviderUpdateForm { + display_name: diesel_required_string_update(cloned_data.display_name.as_deref()), + authorization_endpoint: diesel_required_url_update( + cloned_data.authorization_endpoint.as_deref(), + )?, + token_endpoint: diesel_required_url_update(cloned_data.token_endpoint.as_deref())?, + userinfo_endpoint: diesel_required_url_update(cloned_data.userinfo_endpoint.as_deref())?, + id_claim: diesel_required_string_update(data.id_claim.as_deref()), + client_secret: diesel_required_string_update(data.client_secret.as_deref()), + scopes: diesel_required_string_update(data.scopes.as_deref()), + auto_verify_email: data.auto_verify_email, + account_linking_enabled: data.account_linking_enabled, + enabled: data.enabled, + updated: Some(Some(naive_now())), + }; + + let update_result = + OAuthProvider::update(&mut context.pool(), data.id, &oauth_provider_form).await?; + let oauth_provider = OAuthProvider::read(&mut context.pool(), update_result.id).await?; + Ok(Json(oauth_provider)) +} diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index a0f0b7525..90c68bdbd 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -1,3 +1,4 @@ +use super::convert_published_time; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -92,34 +93,20 @@ pub async fn create_post( .await?; let community_id = data.community_id; - let community = Community::read(&mut context.pool(), community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), community_id).await?; if community.posting_restricted_to_mods { let community_id = data.community_id; - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community_id, local_user_view.local_user.person_id, ) .await?; - if !is_mod { - Err(LemmyErrorType::OnlyModsCanPostInCommunity)? - } } - // Only need to check if language is allowed in case user set it explicitly. When using default - // language, it already only returns allowed languages. - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - data.language_id, - community_id, - ) - .await?; - // attempt to set default language if none was provided let language_id = match data.language_id { - Some(lid) => Some(lid), + Some(lid) => lid, None => { default_post_language( &mut context.pool(), @@ -130,26 +117,41 @@ pub async fn create_post( } }; - let post_form = PostInsertForm::builder() - .name(data.name.trim().to_string()) - .url(url.map(Into::into)) - .body(body) - .alt_text(data.alt_text.clone()) - .community_id(data.community_id) - .creator_id(local_user_view.person.id) - .nsfw(data.nsfw) - .language_id(language_id) - .build(); + // Only need to check if language is allowed in case user set it explicitly. When using default + // language, it already only returns allowed languages. + CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id) + .await?; + + let scheduled_publish_time = + convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?; + let post_form = PostInsertForm { + url: url.map(Into::into), + body, + alt_text: data.alt_text.clone(), + nsfw: data.nsfw, + language_id: Some(language_id), + scheduled_publish_time, + ..PostInsertForm::new( + data.name.trim().to_string(), + local_user_view.person.id, + data.community_id, + ) + }; let inserted_post = Post::create(&mut context.pool(), &post_form) .await .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; + let federate_post = if scheduled_publish_time.is_none() { + send_webmention(inserted_post.clone(), community); + |post| Some(SendActivityData::CreatePost(post)) + } else { + |_| None + }; generate_post_link_metadata( inserted_post.clone(), custom_thumbnail.map(Into::into), - |post| Some(SendActivityData::CreatePost(post)), - Some(local_site), + federate_post, context.reset_request_count(), ) .await?; @@ -169,11 +171,14 @@ pub async fn create_post( mark_post_as_read(person_id, post_id, &mut context.pool()).await?; - if let Some(url) = inserted_post.url.clone() { + build_post_response(&context, community_id, local_user_view, post_id).await +} + +pub fn send_webmention(post: Post, community: Community) { + if let Some(url) = post.url.clone() { if community.visibility == CommunityVisibility::Public { spawn_try_task(async move { - let mut webmention = - Webmention::new::(inserted_post.ap_id.clone().into(), url.clone().into())?; + let mut webmention = Webmention::new::(post.ap_id.clone().into(), url.clone().into())?; webmention.set_checked(true); match webmention .send() @@ -187,6 +192,4 @@ pub async fn create_post( }); } }; - - build_post_response(&context, community_id, local_user_view, post_id).await } diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index 6834030ac..be31759d5 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -21,9 +21,7 @@ pub async fn delete_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let orig_post = Post::read(&mut context.pool(), post_id).await?; // Dont delete it if its already been deleted. if orig_post.deleted == data.deleted { diff --git a/crates/api_crud/src/post/mod.rs b/crates/api_crud/src/post/mod.rs index 8bb842b70..95df9663c 100644 --- a/crates/api_crud/src/post/mod.rs +++ b/crates/api_crud/src/post/mod.rs @@ -1,5 +1,38 @@ +use chrono::{DateTime, TimeZone, Utc}; +use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::source::post::Post; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + pub mod create; pub mod delete; pub mod read; pub mod remove; pub mod update; + +async fn convert_published_time( + scheduled_publish_time: Option, + local_user_view: &LocalUserView, + context: &LemmyContext, +) -> LemmyResult>> { + const MAX_SCHEDULED_POSTS: i64 = 10; + if let Some(scheduled_publish_time) = scheduled_publish_time { + let converted = Utc + .timestamp_opt(scheduled_publish_time, 0) + .single() + .ok_or(LemmyErrorType::InvalidUnixTime)?; + if converted < Utc::now() { + Err(LemmyErrorType::PostScheduleTimeMustBeInFuture)?; + } + if !local_user_view.local_user.admin { + let count = + Post::user_scheduled_post_count(local_user_view.person.id, &mut context.pool()).await?; + if count >= MAX_SCHEDULED_POSTS { + Err(LemmyErrorType::TooManyScheduledPosts)?; + } + } + Ok(Some(converted)) + } else { + Ok(None) + } +} diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index ebf8940a2..7677d59ef 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -21,9 +21,7 @@ pub async fn get_post( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let local_site = SiteView::read_local(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -35,16 +33,14 @@ pub async fn get_post( } else if let Some(comment_id) = data.comment_id { Comment::read(&mut context.pool(), comment_id) .await? - .ok_or(LemmyErrorType::CouldntFindComment)? .post_id } else { - Err(LemmyErrorType::CouldntFindPost)? + Err(LemmyErrorType::NotFound)? }; // Check to see if the person is a mod or admin, to show deleted / removed - let community_id = Post::read(&mut context.pool(), post_id) + let community_id = Post::read_xx(&mut context.pool(), post_id) .await? - .ok_or(LemmyErrorType::CouldntFindPost)? .community_id; let is_mod_or_admin = is_mod_or_admin_opt( @@ -62,8 +58,7 @@ pub async fn get_post( local_user.as_ref(), is_mod_or_admin, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; let post_id = post_view.post.id; if let Some(person_id) = person_id { @@ -85,15 +80,15 @@ pub async fn get_post( local_user.as_ref(), is_mod_or_admin, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; // 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()), + url_only: Some(true), + search_term: Some(url.inner().as_str().into()), local_user: local_user.as_ref(), ..Default::default() } diff --git a/crates/api_crud/src/post/remove.rs b/crates/api_crud/src/post/remove.rs index b4fdba6fb..c53a4552c 100644 --- a/crates/api_crud/src/post/remove.rs +++ b/crates/api_crud/src/post/remove.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ traits::{Crud, Reportable}, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn remove_post( @@ -26,9 +26,7 @@ pub async fn remove_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let orig_post = Post::read(&mut context.pool(), post_id).await?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 835398596..cef8bfea8 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -1,3 +1,4 @@ +use super::{convert_published_time, create::send_webmention}; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -16,6 +17,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{ source::{ actor_language::CommunityLanguage, + community::Community, local_site::LocalSite, post::{Post, PostUpdateForm}, }, @@ -85,9 +87,7 @@ pub async fn update_post( } let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let orig_post = Post::read(&mut context.pool(), post_id).await?; check_community_user_action( &local_user_view.person, @@ -101,13 +101,29 @@ pub async fn update_post( Err(LemmyErrorType::NoPostEditAllowed)? } - let language_id = data.language_id; - CommunityLanguage::is_allowed_community_language( - &mut context.pool(), - language_id, - orig_post.community_id, - ) - .await?; + if let Some(language_id) = data.language_id { + CommunityLanguage::is_allowed_community_language( + &mut context.pool(), + language_id, + orig_post.community_id, + ) + .await?; + } + + // handle changes to scheduled_publish_time + let scheduled_publish_time = match ( + orig_post.scheduled_publish_time, + data.scheduled_publish_time, + ) { + // schedule time can be changed if post is still scheduled (and not published yet) + (Some(_), Some(_)) => { + Some(convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?) + } + // post was scheduled, gets changed to publish immediately + (Some(_), None) => Some(None), + // unchanged + (_, _) => None, + }; let post_form = PostUpdateForm { name: data.name.clone(), @@ -117,6 +133,7 @@ pub async fn update_post( nsfw: data.nsfw, language_id: data.language_id, updated: Some(Some(naive_now())), + scheduled_publish_time, ..Default::default() }; @@ -125,14 +142,36 @@ pub async fn update_post( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; - generate_post_link_metadata( - updated_post.clone(), - custom_thumbnail.flatten().map(Into::into), - |post| Some(SendActivityData::UpdatePost(post)), - Some(local_site), - context.reset_request_count(), - ) - .await?; + // send out federation/webmention if necessary + match ( + orig_post.scheduled_publish_time, + data.scheduled_publish_time, + ) { + // schedule was removed, send create activity and webmention + (Some(_), None) => { + let community = Community::read(&mut context.pool(), orig_post.community_id).await?; + send_webmention(updated_post.clone(), community); + generate_post_link_metadata( + updated_post.clone(), + custom_thumbnail.flatten().map(Into::into), + |post| Some(SendActivityData::CreatePost(post)), + context.reset_request_count(), + ) + .await?; + } + // post was already public, send update + (None, _) => { + generate_post_link_metadata( + updated_post.clone(), + custom_thumbnail.flatten().map(Into::into), + |post| Some(SendActivityData::UpdatePost(post)), + context.reset_request_count(), + ) + .await? + } + // schedule was changed, do nothing + (Some(_), Some(_)) => {} + }; build_post_response( context.deref(), diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 46908da6e..2a49e4ac0 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -5,7 +5,6 @@ use lemmy_api_common::{ private_message::{CreatePrivateMessage, PrivateMessageResponse}, send_activity::{ActivityChannel, SendActivityData}, utils::{ - check_person_block, get_interface_language, get_url_blocklist, local_site_to_slur_regex, @@ -16,6 +15,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{ source::{ local_site::LocalSite, + person_block::PersonBlock, private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, @@ -39,33 +39,29 @@ pub async fn create_private_message( let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; is_valid_body_field(&content, false)?; - check_person_block( - local_user_view.person.id, - data.recipient_id, + PersonBlock::read( &mut context.pool(), + data.recipient_id, + local_user_view.person.id, ) .await?; - let private_message_form = PrivateMessageInsertForm::builder() - .content(content.clone()) - .creator_id(local_user_view.person.id) - .recipient_id(data.recipient_id) - .build(); + let private_message_form = PrivateMessageInsertForm::new( + local_user_view.person.id, + data.recipient_id, + content.clone(), + ); let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form) .await .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?; // Send email to the local recipient, if one exists if view.recipient.local { let recipient_id = data.recipient_id; - let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?; let lang = get_interface_language(&local_recipient); let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname()); let sender_name = &local_user_view.person.name; diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs index dc028ff41..936ff57b8 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -20,9 +20,7 @@ pub async fn delete_private_message( ) -> LemmyResult> { // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.creator_id { Err(LemmyErrorType::EditPrivateMessageNotAllowed)? } @@ -47,9 +45,7 @@ pub async fn delete_private_message( ) .await?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; Ok(Json(PrivateMessageResponse { private_message_view: view, })) diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 364d5c2e3..20eaadb36 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -30,9 +30,7 @@ pub async fn update_private_message( // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; if local_user_view.person.id != orig_private_message.creator_id { Err(LemmyErrorType::EditPrivateMessageNotAllowed)? } @@ -56,9 +54,7 @@ pub async fn update_private_message( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; ActivityChannel::submit_activity( SendActivityData::UpdatePrivateMessage(view.clone()), diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 9dcd1595a..733837de7 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -1,3 +1,4 @@ +use super::not_zero; use crate::site::{application_question_check, site_default_post_listing_type_check}; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair}; use actix_web::web::Json; @@ -5,7 +6,7 @@ use lemmy_api_common::{ context::LemmyContext, site::{CreateSite, SiteResponse}, utils::{ - generate_shared_inbox_url, + generate_inbox_url, get_url_blocklist, is_admin, local_site_rate_limit_to_rate_limit_config, @@ -20,7 +21,6 @@ use lemmy_db_schema::{ local_site::{LocalSite, LocalSiteUpdateForm}, local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm}, site::{Site, SiteUpdateForm}, - tagline::Tagline, }, traits::Crud, utils::{diesel_string_update, diesel_url_create, naive_now}, @@ -55,7 +55,7 @@ pub async fn create_site( validate_create_payload(&local_site, &data)?; let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into(); - let inbox_url = Some(generate_shared_inbox_url(context.settings())?); + let inbox_url = Some(generate_inbox_url()?); let keypair = generate_actor_keypair()?; let slur_regex = local_site_to_slur_regex(&local_site); @@ -90,16 +90,15 @@ pub async fn create_site( let local_site_form = LocalSiteUpdateForm { // Set the site setup to true site_setup: Some(true), - enable_downvotes: data.enable_downvotes, registration_mode: data.registration_mode, - enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, 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, + default_post_sort_type: data.default_post_sort_type, + default_comment_sort_type: data.default_comment_sort_type, 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, @@ -110,6 +109,10 @@ pub async fn create_site( captcha_enabled: data.captcha_enabled, captcha_difficulty: data.captcha_difficulty.clone(), default_post_listing_mode: data.default_post_listing_mode, + post_upvotes: data.post_upvotes, + post_downvotes: data.post_downvotes, + comment_upvotes: data.comment_upvotes, + comment_downvotes: data.comment_downvotes, ..Default::default() }; @@ -117,28 +120,23 @@ pub async fn create_site( let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm { message: data.rate_limit_message, - message_per_second: data.rate_limit_message_per_second, + message_per_second: not_zero(data.rate_limit_message_per_second), post: data.rate_limit_post, - post_per_second: data.rate_limit_post_per_second, + post_per_second: not_zero(data.rate_limit_post_per_second), register: data.rate_limit_register, - register_per_second: data.rate_limit_register_per_second, + register_per_second: not_zero(data.rate_limit_register_per_second), image: data.rate_limit_image, - image_per_second: data.rate_limit_image_per_second, + image_per_second: not_zero(data.rate_limit_image_per_second), comment: data.rate_limit_comment, - comment_per_second: data.rate_limit_comment_per_second, + comment_per_second: not_zero(data.rate_limit_comment_per_second), search: data.rate_limit_search, - search_per_second: data.rate_limit_search_per_second, + search_per_second: not_zero(data.rate_limit_search_per_second), ..Default::default() }; LocalSiteRateLimit::update(&mut context.pool(), &local_site_rate_limit_form).await?; - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; - - let new_taglines = data.taglines.clone(); - let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); @@ -146,7 +144,7 @@ pub async fn create_site( Ok(Json(SiteResponse { site_view, - taglines, + taglines: vec![], })) } @@ -197,13 +195,16 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::site::create::validate_create_payload; use lemmy_api_common::site::CreateSite; - use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode, SortType}; + use lemmy_db_schema::{ + source::local_site::LocalSite, + ListingType, + PostSortType, + RegistrationMode, + }; use lemmy_utils::error::LemmyErrorType; #[test] @@ -212,170 +213,114 @@ mod tests { ( "CreateSite attempted on set up LocalSite", LemmyErrorType::SiteAlreadyExists, - &generate_local_site( - true, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: true, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + ..Default::default() + }, ), ( "CreateSite name matches LocalSite slur filter", LemmyErrorType::Slurs, - &generate_local_site( - false, - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("foo site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("foo site_name"), + ..Default::default() + }, ), ( "CreateSite name matches new slur filter", LemmyErrorType::Slurs, - &generate_local_site( - false, - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("zeta site_name"), - None::, - None::, - None::, - None::, - Some(String::from("(zeta|alpha)")), - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("zeta site_name"), + slur_filter_regex: Some(String::from("(zeta|alpha)")), + ..Default::default() + }, ), ( "CreateSite listing type is Subscribed, which is invalid", LemmyErrorType::InvalidDefaultPostListingType, - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - Some(ListingType::Subscribed), - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + default_post_listing_type: Some(ListingType::Subscribed), + ..Default::default() + }, ), ( "CreateSite is both private and federated", LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - Some(true), - Some(true), - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + private_instance: Some(true), + federation_enabled: Some(true), + ..Default::default() + }, ), ( "LocalSite is private, but CreateSite also makes it federated", LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - Some(true), - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + federation_enabled: Some(true), + ..Default::default() + }, ), ( "CreateSite requires application, but neither it nor LocalSite has an application question", LemmyErrorType::ApplicationQuestionRequired, - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - Some(RegistrationMode::RequireApplication), - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + registration_mode: Some(RegistrationMode::RequireApplication), + ..Default::default() + }, ), ]; @@ -414,95 +359,72 @@ mod tests { let valid_payloads = [ ( "No changes between LocalSite and CreateSite", - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + ..Default::default() + }, ), ( "CreateSite allows clearing and changing values", - &generate_local_site( - false, - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - Some(String::new()), - Some(String::new()), - Some(ListingType::All), - Some(SortType::Active), - Some(String::new()), - Some(false), - Some(true), - Some(String::new()), - Some(RegistrationMode::Open), - ), + &LocalSite { + site_setup: false, + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + sidebar: Some(String::new()), + description: Some(String::new()), + application_question: Some(String::new()), + private_instance: Some(false), + default_post_listing_type: Some(ListingType::All), + default_post_sort_type: Some(PostSortType::Active), + slur_filter_regex: Some(String::new()), + federation_enabled: Some(true), + registration_mode: Some(RegistrationMode::Open), + ..Default::default() + }, ), ( "CreateSite clears existing slur filter regex", - &generate_local_site( - false, - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_create_site( - String::from("foo site_name"), - None::, - None::, - None::, - None::, - Some(String::new()), - None::, - None::, - None::, - None::, - ), + &LocalSite { + site_setup: false, + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("foo site_name"), + slur_filter_regex: Some(String::new()), + ..Default::default() + }, ), ( "LocalSite has application question and CreateSite now requires applications,", - &generate_local_site( - false, - None::, - true, - false, - Some(String::from("question")), - RegistrationMode::Open, - ), - &generate_create_site( - String::from("site_name"), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - Some(RegistrationMode::RequireApplication), - ), + &LocalSite { + site_setup: false, + application_question: Some(String::from("question")), + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &CreateSite { + name: String::from("site_name"), + registration_mode: Some(RegistrationMode::RequireApplication), + ..Default::default() + }, ), ]; @@ -518,84 +440,4 @@ mod tests { ); }) } - - fn generate_local_site( - site_setup: bool, - site_slur_filter_regex: Option, - site_is_private: bool, - site_is_federated: bool, - site_application_question: Option, - site_registration_mode: RegistrationMode, - ) -> LocalSite { - LocalSite { - site_setup, - application_question: site_application_question, - private_instance: site_is_private, - slur_filter_regex: site_slur_filter_regex, - federation_enabled: site_is_federated, - registration_mode: site_registration_mode, - ..Default::default() - } - } - - // Allow the test helper function to have too many arguments. - // It's either this or generate the entire struct each time for testing. - #[allow(clippy::too_many_arguments)] - fn generate_create_site( - site_name: String, - site_description: Option, - site_sidebar: Option, - site_listing_type: Option, - site_sort_type: Option, - site_slur_filter_regex: Option, - site_is_private: Option, - site_is_federated: Option, - site_application_question: Option, - site_registration_mode: Option, - ) -> CreateSite { - CreateSite { - name: site_name, - sidebar: site_sidebar, - description: site_description, - icon: None, - banner: None, - enable_downvotes: None, - enable_nsfw: None, - community_creation_admin_only: None, - require_email_verification: None, - application_question: site_application_question, - private_instance: site_is_private, - default_theme: None, - default_post_listing_type: site_listing_type, - default_sort_type: site_sort_type, - legal_information: None, - application_email_admins: None, - hide_modlog_mod_names: None, - discussion_languages: None, - slur_filter_regex: site_slur_filter_regex, - actor_name_max_length: None, - rate_limit_message: None, - rate_limit_message_per_second: None, - rate_limit_post: None, - rate_limit_post_per_second: None, - rate_limit_register: None, - rate_limit_register_per_second: None, - rate_limit_image: None, - rate_limit_image_per_second: None, - rate_limit_comment: None, - rate_limit_comment_per_second: None, - rate_limit_search: None, - rate_limit_search_per_second: None, - federation_enabled: site_is_federated, - federation_debug: None, - captcha_enabled: None, - captcha_difficulty: None, - allowed_instances: None, - blocked_instances: None, - taglines: None, - registration_mode: site_registration_mode, - content_warning: None, - default_post_listing_mode: None, - } - } } diff --git a/crates/api_crud/src/site/mod.rs b/crates/api_crud/src/site/mod.rs index 0bf7cc279..48b819c38 100644 --- a/crates/api_crud/src/site/mod.rs +++ b/crates/api_crud/src/site/mod.rs @@ -40,12 +40,17 @@ pub fn application_question_check( } } +fn not_zero(val: Option) -> Option { + match val { + Some(0) => None, + v => v, + } +} + #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { - use crate::site::{application_question_check, site_default_post_listing_type_check}; + use crate::site::{application_question_check, not_zero, site_default_post_listing_type_check}; use lemmy_db_schema::{ListingType, RegistrationMode}; #[test] @@ -93,4 +98,11 @@ mod tests { RegistrationMode::RequireApplication ); } + + #[test] + fn test_not_zero() { + assert_eq!(None, not_zero(None)); + assert_eq!(None, not_zero(Some(0))); + assert_eq!(Some(5), not_zero(Some(5))); + } } diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index d2a13ac5e..47fd1f154 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -9,10 +9,11 @@ use lemmy_db_schema::source::{ instance_block::InstanceBlock, language::Language, local_site_url_blocklist::LocalSiteUrlBlocklist, + oauth_provider::OAuthProvider, person_block::PersonBlock, tagline::Tagline, }; -use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; +use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView}; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, @@ -37,16 +38,16 @@ pub async fn get_site( // This data is independent from the user account so we can cache it across requests let mut site_response = CACHE .try_get_with::<_, LemmyError>((), async { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let admins = PersonView::admins(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; - let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?; - let custom_emojis = - CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; + let tagline = Tagline::get_random(&mut context.pool()).await.ok(); + let admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?; + let oauth_providers = + OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone()); + Ok(GetSiteResponse { site_view, admins, @@ -54,16 +55,19 @@ pub async fn get_site( my_user: None, all_languages, discussion_languages, - taglines, - custom_emojis, blocked_urls, + tagline, + oauth_providers: Some(oauth_providers), + admin_oauth_providers: Some(admin_oauth_providers), + taglines: vec![], + custom_emojis: vec![], }) }) .await .map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?; // Build the local user with parallel queries and add it to site response - site_response.my_user = if let Some(local_user_view) = local_user_view { + site_response.my_user = if let Some(ref local_user_view) = local_user_view { let person_id = local_user_view.person.id; let local_user_id = local_user_view.local_user.id; let pool = &mut context.pool(); @@ -86,7 +90,7 @@ pub async fn get_site( .with_lemmy_type(LemmyErrorType::SystemErrLogin)?; Some(MyUserInfo { - local_user_view, + local_user_view: local_user_view.clone(), follows, moderates, community_blocks, @@ -98,5 +102,13 @@ pub async fn get_site( None }; + // filter oauth_providers for public access + if !local_user_view + .map(|l| l.local_user.admin) + .unwrap_or_default() + { + site_response.admin_oauth_providers = None; + } + Ok(Json(site_response)) } diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index f6377038d..cce428cc1 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -1,3 +1,4 @@ +use super::not_zero; use crate::site::{application_question_check, site_default_post_listing_type_check}; use activitypub_federation::config::Data; use actix_web::web::Json; @@ -24,7 +25,6 @@ use lemmy_db_schema::{ local_site_url_blocklist::LocalSiteUrlBlocklist, local_user::LocalUser, site::{Site, SiteUpdateForm}, - tagline::Tagline, }, traits::Crud, utils::{diesel_string_update, diesel_url_update, naive_now}, @@ -52,9 +52,7 @@ pub async fn update_site( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let local_site = site_view.local_site; let site = site_view.site; @@ -101,16 +99,15 @@ pub async fn update_site( .ok(); let local_site_form = LocalSiteUpdateForm { - enable_downvotes: data.enable_downvotes, registration_mode: data.registration_mode, - enable_nsfw: data.enable_nsfw, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, 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, + default_post_sort_type: data.default_post_sort_type, + default_comment_sort_type: data.default_comment_sort_type, 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, @@ -122,6 +119,11 @@ pub async fn update_site( captcha_difficulty: data.captcha_difficulty.clone(), reports_email_admins: data.reports_email_admins, default_post_listing_mode: data.default_post_listing_mode, + oauth_registration: data.oauth_registration, + post_upvotes: data.post_upvotes, + post_downvotes: data.post_downvotes, + comment_upvotes: data.comment_upvotes, + comment_downvotes: data.comment_downvotes, ..Default::default() }; @@ -131,17 +133,17 @@ pub async fn update_site( let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm { message: data.rate_limit_message, - message_per_second: data.rate_limit_message_per_second, + message_per_second: not_zero(data.rate_limit_message_per_second), post: data.rate_limit_post, - post_per_second: data.rate_limit_post_per_second, + post_per_second: not_zero(data.rate_limit_post_per_second), register: data.rate_limit_register, - register_per_second: data.rate_limit_register_per_second, + register_per_second: not_zero(data.rate_limit_register_per_second), image: data.rate_limit_image, - image_per_second: data.rate_limit_image_per_second, + image_per_second: not_zero(data.rate_limit_image_per_second), comment: data.rate_limit_comment, - comment_per_second: data.rate_limit_comment_per_second, + comment_per_second: not_zero(data.rate_limit_comment_per_second), search: data.rate_limit_search, - search_per_second: data.rate_limit_search_per_second, + search_per_second: not_zero(data.rate_limit_search_per_second), ..Default::default() }; @@ -188,12 +190,7 @@ pub async fn update_site( .with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?; } - let new_taglines = data.taglines.clone(); - let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?; - - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); @@ -201,7 +198,7 @@ pub async fn update_site( Ok(Json(SiteResponse { site_view, - taglines, + taglines: vec![], })) } @@ -250,13 +247,16 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::site::update::validate_update_payload; use lemmy_api_common::site::EditSite; - use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode, SortType}; + use lemmy_db_schema::{ + source::local_site::LocalSite, + ListingType, + PostSortType, + RegistrationMode, + }; use lemmy_utils::error::LemmyErrorType; #[test] @@ -265,140 +265,94 @@ mod tests { ( "EditSite name matches LocalSite slur filter", LemmyErrorType::Slurs, - &generate_local_site( - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("foo site_name")), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("foo site_name")), + ..Default::default() + }, ), ( "EditSite name matches new slur filter", LemmyErrorType::Slurs, - &generate_local_site( - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("zeta site_name")), - None::, - None::, - None::, - None::, - Some(String::from("(zeta|alpha)")), - None::, - None::, - None::, - None::, - ), + &LocalSite { + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("zeta site_name")), + slur_filter_regex: Some(String::from("(zeta|alpha)")), + ..Default::default() + }, ), ( "EditSite listing type is Subscribed, which is invalid", LemmyErrorType::InvalidDefaultPostListingType, - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - None::, - None::, - Some(ListingType::Subscribed), - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + default_post_listing_type: Some(ListingType::Subscribed), + ..Default::default() + }, ), ( "EditSite is both private and federated", LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - None::, - None::, - None::, - None::, - None::, - Some(true), - Some(true), - None::, - None::, - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + private_instance: Some(true), + federation_enabled: Some(true), + ..Default::default() + }, ), ( "LocalSite is private, but EditSite also makes it federated", LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether, - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - None::, - None::, - None::, - None::, - None::, - None::, - Some(true), - None::, - None::, - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + federation_enabled: Some(true), + ..Default::default() + }, ), ( "EditSite requires application, but neither it nor LocalSite has an application question", LemmyErrorType::ApplicationQuestionRequired, - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - Some(RegistrationMode::RequireApplication), - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + registration_mode: Some(RegistrationMode::RequireApplication), + ..Default::default() + }, ), ]; @@ -434,91 +388,65 @@ mod tests { let valid_payloads = [ ( "No changes between LocalSite and EditSite", - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite::default(), ), ( "EditSite allows clearing and changing values", - &generate_local_site( - None::, - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - Some(String::new()), - Some(String::new()), - Some(ListingType::All), - Some(SortType::Active), - Some(String::new()), - Some(false), - Some(true), - Some(String::new()), - Some(RegistrationMode::Open), - ), + &LocalSite { + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + sidebar: Some(String::new()), + description: Some(String::new()), + application_question: Some(String::new()), + private_instance: Some(false), + default_post_listing_type: Some(ListingType::All), + default_post_sort_type: Some(PostSortType::Active), + slur_filter_regex: Some(String::new()), + registration_mode: Some(RegistrationMode::Open), + federation_enabled: Some(true), + ..Default::default() + }, ), ( "EditSite name passes slur filter regex", - &generate_local_site( - Some(String::from("(foo|bar)")), - true, - false, - None::, - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("foo site_name")), - None::, - None::, - None::, - None::, - Some(String::new()), - None::, - None::, - None::, - None::, - ), + &LocalSite { + private_instance: true, + slur_filter_regex: Some(String::from("(foo|bar)")), + registration_mode: RegistrationMode::Open, + federation_enabled: false, + ..Default::default() + }, + &EditSite { + name: Some(String::from("foo site_name")), + slur_filter_regex: Some(String::new()), + ..Default::default() + }, ), ( "LocalSite has application question and EditSite now requires applications,", - &generate_local_site( - None::, - true, - false, - Some(String::from("question")), - RegistrationMode::Open, - ), - &generate_edit_site( - Some(String::from("site_name")), - None::, - None::, - None::, - None::, - None::, - None::, - None::, - None::, - Some(RegistrationMode::RequireApplication), - ), + &LocalSite { + application_question: Some(String::from("question")), + private_instance: true, + federation_enabled: false, + registration_mode: RegistrationMode::Open, + ..Default::default() + }, + &EditSite { + name: Some(String::from("site_name")), + registration_mode: Some(RegistrationMode::RequireApplication), + ..Default::default() + }, ), ]; @@ -534,84 +462,4 @@ mod tests { ); }) } - - fn generate_local_site( - site_slur_filter_regex: Option, - site_is_private: bool, - site_is_federated: bool, - site_application_question: Option, - site_registration_mode: RegistrationMode, - ) -> LocalSite { - LocalSite { - application_question: site_application_question, - private_instance: site_is_private, - slur_filter_regex: site_slur_filter_regex, - federation_enabled: site_is_federated, - registration_mode: site_registration_mode, - ..Default::default() - } - } - - // Allow the test helper function to have too many arguments. - // It's either this or generate the entire struct each time for testing. - #[allow(clippy::too_many_arguments)] - fn generate_edit_site( - site_name: Option, - site_description: Option, - site_sidebar: Option, - site_listing_type: Option, - site_sort_type: Option, - site_slur_filter_regex: Option, - site_is_private: Option, - site_is_federated: Option, - site_application_question: Option, - site_registration_mode: Option, - ) -> EditSite { - EditSite { - name: site_name, - sidebar: site_sidebar, - description: site_description, - icon: None, - banner: None, - enable_downvotes: None, - enable_nsfw: None, - community_creation_admin_only: None, - require_email_verification: None, - application_question: site_application_question, - private_instance: site_is_private, - default_theme: None, - default_post_listing_type: site_listing_type, - default_sort_type: site_sort_type, - legal_information: None, - application_email_admins: None, - hide_modlog_mod_names: None, - discussion_languages: None, - slur_filter_regex: site_slur_filter_regex, - actor_name_max_length: None, - rate_limit_message: None, - rate_limit_message_per_second: None, - rate_limit_post: None, - rate_limit_post_per_second: None, - rate_limit_register: None, - rate_limit_register_per_second: None, - rate_limit_image: None, - rate_limit_image_per_second: None, - rate_limit_comment: None, - rate_limit_comment_per_second: None, - rate_limit_search: None, - rate_limit_search_per_second: None, - federation_enabled: site_is_federated, - federation_debug: None, - captcha_enabled: None, - captcha_difficulty: None, - allowed_instances: None, - blocked_instances: None, - blocked_urls: None, - taglines: None, - registration_mode: site_registration_mode, - reports_email_admins: None, - content_warning: None, - default_post_listing_mode: None, - } - } } diff --git a/crates/api_crud/src/tagline/create.rs b/crates/api_crud/src/tagline/create.rs new file mode 100644 index 000000000..f67a26f68 --- /dev/null +++ b/crates/api_crud/src/tagline/create.rs @@ -0,0 +1,38 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{CreateTagline, TaglineResponse}, + utils::{get_url_blocklist, is_admin, local_site_to_slur_regex, process_markdown}, +}; +use lemmy_db_schema::{ + source::{ + local_site::LocalSite, + tagline::{Tagline, TaglineInsertForm}, + }, + traits::Crud, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn create_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let local_site = LocalSite::read(&mut context.pool()).await?; + + 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?; + + let tagline_form = TaglineInsertForm { content }; + + let tagline = Tagline::create(&mut context.pool(), &tagline_form).await?; + + Ok(Json(TaglineResponse { tagline })) +} diff --git a/crates/api_crud/src/tagline/delete.rs b/crates/api_crud/src/tagline/delete.rs new file mode 100644 index 000000000..9add3cfe6 --- /dev/null +++ b/crates/api_crud/src/tagline/delete.rs @@ -0,0 +1,25 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::DeleteTagline, + utils::is_admin, + SuccessResponse, +}; +use lemmy_db_schema::{source::tagline::Tagline, traits::Crud}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn delete_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + Tagline::delete(&mut context.pool(), data.id).await?; + + Ok(Json(SuccessResponse::default())) +} diff --git a/crates/api_crud/src/tagline/list.rs b/crates/api_crud/src/tagline/list.rs new file mode 100644 index 000000000..21929f547 --- /dev/null +++ b/crates/api_crud/src/tagline/list.rs @@ -0,0 +1,19 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{ListTaglines, ListTaglinesResponse}, +}; +use lemmy_db_schema::source::tagline::Tagline; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn list_taglines( + data: Query, + local_user_view: Option, + context: Data, +) -> Result, LemmyError> { + let taglines = Tagline::list(&mut context.pool(), data.page, data.limit).await?; + + Ok(Json(ListTaglinesResponse { taglines })) +} diff --git a/crates/api_crud/src/tagline/mod.rs b/crates/api_crud/src/tagline/mod.rs new file mode 100644 index 000000000..ffd48daf6 --- /dev/null +++ b/crates/api_crud/src/tagline/mod.rs @@ -0,0 +1,4 @@ +pub mod create; +pub mod delete; +pub mod list; +pub mod update; diff --git a/crates/api_crud/src/tagline/update.rs b/crates/api_crud/src/tagline/update.rs new file mode 100644 index 000000000..043589d26 --- /dev/null +++ b/crates/api_crud/src/tagline/update.rs @@ -0,0 +1,42 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{TaglineResponse, UpdateTagline}, + utils::{get_url_blocklist, is_admin, local_site_to_slur_regex, process_markdown}, +}; +use lemmy_db_schema::{ + source::{ + local_site::LocalSite, + tagline::{Tagline, TaglineUpdateForm}, + }, + traits::Crud, + utils::naive_now, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn update_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let local_site = LocalSite::read(&mut context.pool()).await?; + + 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?; + + let tagline_form = TaglineUpdateForm { + content, + updated: naive_now(), + }; + + let tagline = Tagline::update(&mut context.pool(), data.id, &tagline_form).await?; + + Ok(Json(TaglineResponse { tagline })) +} diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 64bef8760..ed560e3d6 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -3,11 +3,14 @@ use actix_web::{web::Json, HttpRequest}; use lemmy_api_common::{ claims::Claims, context::LemmyContext, + oauth_provider::AuthenticateWithOauth, person::{LoginResponse, Register}, utils::{ + check_email_verified, + check_registration_application, + check_user_valid, generate_inbox_url, generate_local_apub_endpoint, - generate_shared_inbox_url, honeypot_check, local_site_to_slur_regex, password_length_check, @@ -18,11 +21,15 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ aggregates::structs::PersonAggregates, + newtypes::{InstanceId, OAuthProviderId}, source::{ captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer}, language::Language, + local_site::LocalSite, local_user::{LocalUser, LocalUserInsertForm}, local_user_vote_display_mode::LocalUserVoteDisplayMode, + oauth_account::{OAuthAccount, OAuthAccountInsertForm}, + oauth_provider::OAuthProvider, person::{Person, PersonInsertForm}, registration_application::{RegistrationApplication, RegistrationApplicationInsertForm}, }, @@ -31,23 +38,33 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::is_valid_actor_name, }, }; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use std::collections::HashSet; -#[tracing::instrument(skip(context))] +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +/// Response from OAuth token endpoint +struct TokenResponse { + pub access_token: String, + pub token_type: String, + pub expires_in: Option, + pub refresh_token: Option, + pub scope: Option, +} + pub async fn register( data: Json, req: HttpRequest, context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let local_site = site_view.local_site; let require_registration_application = local_site.registration_mode == RegistrationMode::RequireApplication; @@ -63,8 +80,9 @@ pub async fn register( Err(LemmyErrorType::EmailRequired)? } - if local_site.site_setup && require_registration_application && data.answer.is_none() { - Err(LemmyErrorType::RegistrationApplicationAnswerRequired)? + // make sure the registration answer is provided when the registration application is required + if local_site.site_setup { + validate_registration_answer(require_registration_application, &data.answer)?; } // Make sure passwords match @@ -73,86 +91,50 @@ pub async fn register( } if local_site.site_setup && local_site.captcha_enabled { - if let Some(captcha_uuid) = &data.captcha_uuid { - let uuid = uuid::Uuid::parse_str(captcha_uuid)?; - let check = CaptchaAnswer::check_captcha( - &mut context.pool(), - CheckCaptchaAnswer { - uuid, - answer: data.captcha_answer.clone().unwrap_or_default(), - }, - ) - .await?; - if !check { - Err(LemmyErrorType::CaptchaIncorrect)? - } - } else { - Err(LemmyErrorType::CaptchaIncorrect)? - } + let uuid = uuid::Uuid::parse_str(&data.captcha_uuid.clone().unwrap_or_default())?; + CaptchaAnswer::check_captcha( + &mut context.pool(), + CheckCaptchaAnswer { + uuid, + answer: data.captcha_answer.clone().unwrap_or_default(), + }, + ) + .await?; } let slur_regex = local_site_to_slur_regex(&local_site); check_slurs(&data.username, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?; - let actor_keypair = generate_actor_keypair()?; - is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?; - let actor_id = generate_local_apub_endpoint( - EndpointType::Person, - &data.username, - &context.settings().get_protocol_and_hostname(), - )?; + Person::check_username_taken(&mut context.pool(), &data.username).await?; if let Some(email) = &data.email { - if LocalUser::is_email_taken(&mut context.pool(), email).await? { - Err(LemmyErrorType::EmailAlreadyExists)? - } + LocalUser::check_is_email_taken(&mut context.pool(), email).await?; } // We have to create both a person, and local_user - - // Register the new person - 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) - .await - .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; + let inserted_person = create_person( + data.username.clone(), + &local_site, + site_view.site.instance_id, + &context, + ) + .await?; // Automatically set their application as accepted, if they created this with open registration. // Also fixes a bug which allows users to log in when registrations are changed to closed. let accepted_application = Some(!require_registration_application); - // Get the user's preferred language using the Accept-Language header - let language_tags: Vec = req - .headers() - .get("Accept-Language") - .map(|hdr| accept_language::parse(hdr.to_str().unwrap_or_default())) - .iter() - .flatten() - // Remove the optional region code - .map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string()) - .collect(); - // Show nsfw content if param is true, or if content_warning exists let show_nsfw = data .show_nsfw .unwrap_or(site_view.site.content_warning.is_some()); + let language_tags = get_language_tags(&req); + // Create the local user let local_user_form = LocalUserInsertForm { email: data.email.as_deref().map(str::to_lowercase), - password_encrypted: data.password.to_string(), show_nsfw: Some(show_nsfw), accepted_application, default_listing_type: Some(local_site.default_post_listing_type), @@ -160,21 +142,10 @@ pub async fn register( interface_language: language_tags.first().cloned(), // If its the initial site setup, they are an admin admin: Some(!local_site.site_setup), - ..LocalUserInsertForm::new(inserted_person.id, data.password.to_string()) + ..LocalUserInsertForm::new(inserted_person.id, Some(data.password.to_string())) }; - let all_languages = Language::read_all(&mut context.pool()).await?; - // use hashset to avoid duplicates - let mut language_ids = HashSet::new(); - for l in language_tags { - if let Some(found) = all_languages.iter().find(|all| all.code == l) { - language_ids.insert(found.id); - } - } - let language_ids = language_ids.into_iter().collect(); - - let inserted_local_user = - LocalUser::create(&mut context.pool(), &local_user_form, language_ids).await?; + let inserted_local_user = create_local_user(&context, language_tags, &local_user_form).await?; if local_site.site_setup && require_registration_application { // Create the registration application @@ -207,29 +178,13 @@ pub async fn register( let jwt = Claims::generate(inserted_local_user.id, req, &context).await?; login_response.jwt = Some(jwt); } else { - if local_site.require_email_verification { - let local_user_view = LocalUserView { - local_user: inserted_local_user, - local_user_vote_display_mode: LocalUserVoteDisplayMode::default(), - person: inserted_person, - counts: PersonAggregates::default(), - }; - // we check at the beginning of this method that email is set - let email = local_user_view - .local_user - .email - .clone() - .expect("email was provided"); - - send_verification_email( - &local_user_view, - &email, - &mut context.pool(), - context.settings(), - ) - .await?; - login_response.verify_email_sent = true; - } + login_response.verify_email_sent = send_verification_email_if_required( + &context, + &local_site, + &inserted_local_user, + &inserted_person, + ) + .await?; if require_registration_application { login_response.registration_created = true; @@ -238,3 +193,386 @@ pub async fn register( Ok(Json(login_response)) } + +#[tracing::instrument(skip(context))] +pub async fn authenticate_with_oauth( + data: Json, + req: HttpRequest, + context: Data, +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()).await?; + let local_site = site_view.local_site.clone(); + + // validate inputs + if data.oauth_provider_id == OAuthProviderId(0) || data.code.is_empty() || data.code.len() > 300 { + return Err(LemmyErrorType::OauthAuthorizationInvalid)?; + } + + // validate the redirect_uri + let redirect_uri = &data.redirect_uri; + if redirect_uri.host_str().unwrap_or("").is_empty() + || !redirect_uri.path().eq(&String::from("/oauth/callback")) + || !redirect_uri.query().unwrap_or("").is_empty() + { + Err(LemmyErrorType::OauthAuthorizationInvalid)? + } + + // Fetch the OAUTH provider and make sure it's enabled + let oauth_provider_id = data.oauth_provider_id; + let oauth_provider = OAuthProvider::read(&mut context.pool(), oauth_provider_id) + .await + .ok() + .ok_or(LemmyErrorType::OauthAuthorizationInvalid)?; + + if !oauth_provider.enabled { + return Err(LemmyErrorType::OauthAuthorizationInvalid)?; + } + + let token_response = + oauth_request_access_token(&context, &oauth_provider, &data.code, redirect_uri.as_str()) + .await?; + + let user_info = oidc_get_user_info( + &context, + &oauth_provider, + token_response.access_token.as_str(), + ) + .await?; + + let oauth_user_id = read_user_info(&user_info, oauth_provider.id_claim.as_str())?; + + let mut login_response = LoginResponse { + jwt: None, + registration_created: false, + verify_email_sent: false, + }; + + // Lookup user by oauth_user_id + let mut local_user_view = + LocalUserView::find_by_oauth_id(&mut context.pool(), oauth_provider.id, &oauth_user_id).await; + + let local_user: LocalUser; + if let Ok(user_view) = local_user_view { + // user found by oauth_user_id => Login user + local_user = user_view.clone().local_user; + + check_user_valid(&user_view.person)?; + check_email_verified(&user_view, &site_view)?; + check_registration_application(&user_view, &site_view.local_site, &mut context.pool()).await?; + } else { + // user has never previously registered using oauth + + // prevent registration if registration is closed + if local_site.registration_mode == RegistrationMode::Closed { + Err(LemmyErrorType::RegistrationClosed)? + } + + // prevent registration if registration is closed for OAUTH providers + if !local_site.oauth_registration { + return Err(LemmyErrorType::OauthRegistrationClosed)?; + } + + // Extract the OAUTH email claim from the returned user_info + let email = read_user_info(&user_info, "email")?; + + let require_registration_application = + local_site.registration_mode == RegistrationMode::RequireApplication; + + // Lookup user by OAUTH email and link accounts + local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email).await; + + let person; + if let Ok(user_view) = local_user_view { + // user found by email => link and login if linking is allowed + + // we only allow linking by email when email_verification is required otherwise emails cannot + // be trusted + if oauth_provider.account_linking_enabled && site_view.local_site.require_email_verification { + // WARNING: + // If an admin switches the require_email_verification config from false to true, + // users who signed up before the switch could have accounts with unverified emails falsely + // marked as verified. + + check_user_valid(&user_view.person)?; + check_email_verified(&user_view, &site_view)?; + check_registration_application(&user_view, &site_view.local_site, &mut context.pool()) + .await?; + + // Link with OAUTH => Login user + let oauth_account_form = + OAuthAccountInsertForm::new(user_view.local_user.id, oauth_provider.id, oauth_user_id); + + OAuthAccount::create(&mut context.pool(), &oauth_account_form) + .await + .map_err(|_| LemmyErrorType::OauthLoginFailed)?; + + local_user = user_view.local_user.clone(); + } else { + return Err(LemmyErrorType::EmailAlreadyExists)?; + } + } else { + // No user was found by email => Register as new user + + // make sure the registration answer is provided when the registration application is required + validate_registration_answer(require_registration_application, &data.answer)?; + + // make sure the username is provided + let username = data + .username + .as_ref() + .ok_or(LemmyErrorType::RegistrationUsernameRequired)?; + + let slur_regex = local_site_to_slur_regex(&local_site); + check_slurs(username, &slur_regex)?; + check_slurs_opt(&data.answer, &slur_regex)?; + + Person::check_username_taken(&mut context.pool(), username).await?; + + // We have to create a person, a local_user, and an oauth_account + person = create_person( + username.clone(), + &local_site, + site_view.site.instance_id, + &context, + ) + .await?; + + // Show nsfw content if param is true, or if content_warning exists + let show_nsfw = data + .show_nsfw + .unwrap_or(site_view.site.content_warning.is_some()); + + let language_tags = get_language_tags(&req); + + // Create the local user + let local_user_form = LocalUserInsertForm { + email: Some(str::to_lowercase(&email)), + show_nsfw: Some(show_nsfw), + accepted_application: Some(!require_registration_application), + email_verified: Some(oauth_provider.auto_verify_email), + post_listing_mode: Some(local_site.default_post_listing_mode), + interface_language: language_tags.first().cloned(), + // If its the initial site setup, they are an admin + admin: Some(!local_site.site_setup), + ..LocalUserInsertForm::new(person.id, None) + }; + + local_user = create_local_user(&context, language_tags, &local_user_form).await?; + + // Create the oauth account + let oauth_account_form = + OAuthAccountInsertForm::new(local_user.id, oauth_provider.id, oauth_user_id); + + OAuthAccount::create(&mut context.pool(), &oauth_account_form) + .await + .map_err(|_| LemmyErrorType::IncorrectLogin)?; + + // prevent sign in until application is accepted + if local_site.site_setup + && require_registration_application + && !local_user.accepted_application + && !local_user.admin + { + // Create the registration application + RegistrationApplication::create( + &mut context.pool(), + &RegistrationApplicationInsertForm { + local_user_id: local_user.id, + answer: data.answer.clone().expect("must have an answer"), + }, + ) + .await?; + + login_response.registration_created = true; + } + + // Check email is verified when required + login_response.verify_email_sent = + send_verification_email_if_required(&context, &local_site, &local_user, &person).await?; + } + } + + if !login_response.registration_created && !login_response.verify_email_sent { + let jwt = Claims::generate(local_user.id, req, &context).await?; + login_response.jwt = Some(jwt); + } + + return Ok(Json(login_response)); +} + +async fn create_person( + username: String, + local_site: &LocalSite, + instance_id: InstanceId, + context: &Data, +) -> Result { + let actor_keypair = generate_actor_keypair()?; + is_valid_actor_name(&username, local_site.actor_name_max_length as usize)?; + let actor_id = generate_local_apub_endpoint( + EndpointType::Person, + &username, + &context.settings().get_protocol_and_hostname(), + )?; + + // Register the new person + let person_form = PersonInsertForm { + actor_id: Some(actor_id.clone()), + inbox_url: Some(generate_inbox_url()?), + private_key: Some(actor_keypair.private_key), + ..PersonInsertForm::new(username.clone(), actor_keypair.public_key, instance_id) + }; + + // insert the person + let inserted_person = Person::create(&mut context.pool(), &person_form) + .await + .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; + + Ok(inserted_person) +} + +fn get_language_tags(req: &HttpRequest) -> Vec { + req + .headers() + .get("Accept-Language") + .map(|hdr| accept_language::parse(hdr.to_str().unwrap_or_default())) + .iter() + .flatten() + // Remove the optional region code + .map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string()) + .collect::>() +} + +async fn create_local_user( + context: &Data, + language_tags: Vec, + local_user_form: &LocalUserInsertForm, +) -> Result { + let all_languages = Language::read_all(&mut context.pool()).await?; + // use hashset to avoid duplicates + let mut language_ids = HashSet::new(); + for l in language_tags { + if let Some(found) = all_languages.iter().find(|all| all.code == l) { + language_ids.insert(found.id); + } + } + let language_ids = language_ids.into_iter().collect(); + + let inserted_local_user = + LocalUser::create(&mut context.pool(), local_user_form, language_ids).await?; + + Ok(inserted_local_user) +} + +async fn send_verification_email_if_required( + context: &Data, + local_site: &LocalSite, + local_user: &LocalUser, + person: &Person, +) -> LemmyResult { + let mut sent = false; + if !local_user.admin && local_site.require_email_verification && !local_user.email_verified { + let local_user_view = LocalUserView { + local_user: local_user.clone(), + local_user_vote_display_mode: LocalUserVoteDisplayMode::default(), + person: person.clone(), + counts: PersonAggregates::default(), + }; + + send_verification_email( + &local_user_view, + &local_user + .email + .clone() + .expect("invalid verification email"), + &mut context.pool(), + context.settings(), + ) + .await?; + + sent = true; + } + Ok(sent) +} + +fn validate_registration_answer( + require_registration_application: bool, + answer: &Option, +) -> LemmyResult<()> { + if require_registration_application && answer.is_none() { + Err(LemmyErrorType::RegistrationApplicationAnswerRequired)? + } + + Ok(()) +} + +async fn oauth_request_access_token( + context: &Data, + oauth_provider: &OAuthProvider, + code: &str, + redirect_uri: &str, +) -> LemmyResult { + // Request an Access Token from the OAUTH provider + let response = context + .client() + .post(oauth_provider.token_endpoint.as_str()) + .header("Accept", "application/json") + .form(&[ + ("grant_type", "authorization_code"), + ("code", code), + ("redirect_uri", redirect_uri), + ("client_id", &oauth_provider.client_id), + ("client_secret", &oauth_provider.client_secret), + ]) + .send() + .await; + + let response = response.map_err(|_| LemmyErrorType::OauthLoginFailed)?; + if !response.status().is_success() { + Err(LemmyErrorType::OauthLoginFailed)?; + } + + // Extract the access token + let token_response = response + .json::() + .await + .map_err(|_| LemmyErrorType::OauthLoginFailed)?; + + Ok(token_response) +} + +async fn oidc_get_user_info( + context: &Data, + oauth_provider: &OAuthProvider, + access_token: &str, +) -> LemmyResult { + // Request the user info from the OAUTH provider + let response = context + .client() + .get(oauth_provider.userinfo_endpoint.as_str()) + .header("Accept", "application/json") + .bearer_auth(access_token) + .send() + .await; + + let response = response.map_err(|_| LemmyErrorType::OauthLoginFailed)?; + if !response.status().is_success() { + Err(LemmyErrorType::OauthLoginFailed)?; + } + + // Extract the OAUTH user_id claim from the returned user_info + let user_info = response + .json::() + .await + .map_err(|_| LemmyErrorType::OauthLoginFailed)?; + + Ok(user_info) +} + +fn read_user_info(user_info: &serde_json::Value, key: &str) -> LemmyResult { + if let Some(value) = user_info.get(key) { + let result = serde_json::from_value::(value.clone()) + .map_err(|_| LemmyErrorType::OauthLoginFailed)?; + return Ok(result); + } + Err(LemmyErrorType::OauthLoginFailed)? +} diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs index 363230d83..d1825425c 100644 --- a/crates/api_crud/src/user/delete.rs +++ b/crates/api_crud/src/user/delete.rs @@ -8,7 +8,11 @@ use lemmy_api_common::{ utils::purge_user_account, SuccessResponse, }; -use lemmy_db_schema::source::{login_token::LoginToken, person::Person}; +use lemmy_db_schema::source::{ + login_token::LoginToken, + oauth_account::OAuthAccount, + person::Person, +}; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; @@ -19,11 +23,12 @@ pub async fn delete_account( local_user_view: LocalUserView, ) -> LemmyResult> { // Verify the password - let valid: bool = verify( - &data.password, - &local_user_view.local_user.password_encrypted, - ) - .unwrap_or(false); + let valid: bool = local_user_view + .local_user + .password_encrypted + .as_ref() + .and_then(|password_encrypted| verify(&data.password, password_encrypted).ok()) + .unwrap_or(false); if !valid { Err(LemmyErrorType::IncorrectLogin)? } @@ -31,6 +36,7 @@ pub async fn delete_account( if data.delete_content { purge_user_account(local_user_view.person.id, &context).await?; } else { + OAuthAccount::delete_user_accounts(&mut context.pool(), local_user_view.local_user.id).await?; Person::delete_account(&mut context.pool(), local_user_view.person.id).await?; } diff --git a/crates/apub/assets/lemmy/objects/group.json b/crates/apub/assets/lemmy/objects/group.json index 1b848a866..bd6e44065 100644 --- a/crates/apub/assets/lemmy/objects/group.json +++ b/crates/apub/assets/lemmy/objects/group.json @@ -3,9 +3,9 @@ "type": "Group", "preferredUsername": "tenforward", "name": "Ten Forward", - "summary": "

Lounge and recreation facility

\n
\n

Welcome to the Enterprise!.

\n", + "summary": "

Lounge and recreation facility

\n
\n

Welcome to the Enterprise!.

\n", "source": { - "content": "Lounge and recreation facility\n\n---\n\nWelcome to the [Enterprise](https://memory-alpha.fandom.com/wiki/USS_Enterprise_(NCC-1701-D))!.", + "content": "Lounge and recreation facility\n\n---\n\nWelcome to the Enterprise!", "mediaType": "text/markdown" }, "sensitive": false, diff --git a/crates/apub/assets/pleroma/objects/note.json b/crates/apub/assets/pleroma/objects/note.json index ff4b20d25..af61ff46e 100644 --- a/crates/apub/assets/pleroma/objects/note.json +++ b/crates/apub/assets/pleroma/objects/note.json @@ -10,7 +10,7 @@ "attachment": [], "attributedTo": "https://queer.hacktivis.me/users/lanodan", "cc": ["https://www.w3.org/ns/activitystreams#Public"], - "content": "@popolon Have what?", + "content": "Have what?", "context": "https://queer.hacktivis.me/contexts/34cba3d2-2f35-4169-aeff-56af9bfeb753", "conversation": "https://queer.hacktivis.me/contexts/34cba3d2-2f35-4169-aeff-56af9bfeb753", "id": "https://queer.hacktivis.me/objects/8d4973f4-53de-49cd-8c27-df160e16a9c2", diff --git a/crates/apub/assets/pleroma/objects/person.json b/crates/apub/assets/pleroma/objects/person.json index bc9008bab..fff9a2cba 100644 --- a/crates/apub/assets/pleroma/objects/person.json +++ b/crates/apub/assets/pleroma/objects/person.json @@ -41,7 +41,7 @@ "owner": "https://queer.hacktivis.me/users/lanodan", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsWOgdjSMc010qvxC3njI\nXJlFWMJ5gJ8QXCW/PajYdsHPM6d+jxBNJ6zp9/tIRa2m7bWHTSkuHQ7QthOpt6vu\n+dAWpKRLS607SPLItn/qUcyXvgN+H8shfyhMxvkVs9jXdtlBsLUVE7UNpN0dxzqe\nI79QWbf7o4amgaIWGRYB+OYMnIxKt+GzIkivZdSVSYjfxNnBYkMCeUxm5EpPIxKS\nP5bBHAVRRambD5NUmyKILuC60/rYuc/C+vmgpY2HCWFS2q6o34dPr9enwL6t4b3m\nS1t/EJHk9rGaaDqSGkDEfyQI83/7SDebWKuETMKKFLZi1vMgQIFuOYCIhN6bIiZm\npQIDAQAB\n-----END PUBLIC KEY-----\n\n" }, - "summary": "---
Website: https://hacktivis.me/
Lang: Français(natif), English(fluent), LSF(🤏~👌), русский (еле-еле),
Politics: Anarchist as in DIY/DIWO, freedom of association, anti-authoritarian, anti-identitarianism

Pronouns: meh, pick any, have fun
Timezone: Let's say Mars, I have a non-24h cycle
```
🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:
Pleroma maintainer (mostly backend)
BadWolf developer
Gentoo contributor

Dayjob: yogoko.fr

That person which uses HJKL in games

Just because computer bad: X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db
Federation-bots: #nobot", + "summary": "---Lang: Français(natif), English(fluent), LSF(🤏~👌), русский (еле-еле),
Politics: Anarchist as in DIY/DIWO, freedom of association, anti-authoritarian, anti-identitarianism

Pronouns: meh, pick any, have fun
Timezone: Let's say Mars, I have a non-24h cycle
```
🦊🦄⚧🂡ⓥ :anarchy: 👿🐧 :gentoo:
Pleroma maintainer (mostly backend)
BadWolf developer
Gentoo contributor

Dayjob: yogoko.fr

That person which uses HJKL in games

Just because computer bad: X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

banner from: https://soc.flyingcube.tech/objects/56f79be2-9013-4559-9826-f7dc392417db
Federation-bots: #nobot", "tag": [ { "icon": { diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index a65082b90..64d5e7816 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -23,7 +23,7 @@ use anyhow::anyhow; use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{remove_user_data, remove_user_data_in_community}, + utils::{remove_or_restore_user_data, remove_or_restore_user_data_in_community}, }; use lemmy_db_schema::{ source::{ @@ -157,6 +157,7 @@ impl ActivityHandler for BlockUser { let mod_person = self.actor.dereference(context).await?; let blocked_person = self.object.dereference(context).await?; let target = self.target.dereference(context).await?; + let reason = self.summary; match target { SiteOrCommunity::Site(_site) => { let blocked_person = Person::update( @@ -170,14 +171,15 @@ impl ActivityHandler for BlockUser { ) .await?; if self.remove_data.unwrap_or(false) { - remove_user_data(blocked_person.id, context).await?; + remove_or_restore_user_data(mod_person.id, blocked_person.id, true, &reason, context) + .await?; } // write mod log let form = ModBanForm { mod_person_id: mod_person.id, other_person_id: blocked_person.id, - reason: self.summary, + reason, banned: Some(true), expires, }; @@ -202,8 +204,15 @@ impl ActivityHandler for BlockUser { .ok(); if self.remove_data.unwrap_or(false) { - remove_user_data_in_community(community.id, blocked_person.id, &mut context.pool()) - .await?; + remove_or_restore_user_data_in_community( + community.id, + mod_person.id, + blocked_person.id, + true, + &reason, + &mut context.pool(), + ) + .await?; } // write to mod log @@ -211,7 +220,7 @@ impl ActivityHandler for BlockUser { mod_person_id: mod_person.id, other_person_id: blocked_person.id, community_id: community.id, - reason: self.summary, + reason, banned: Some(true), expires, }; diff --git a/crates/apub/src/activities/block/mod.rs b/crates/apub/src/activities/block/mod.rs index d42b62369..c8323fcb4 100644 --- a/crates/apub/src/activities/block/mod.rs +++ b/crates/apub/src/activities/block/mod.rs @@ -22,11 +22,7 @@ use lemmy_db_schema::{ traits::Crud, utils::DbPool, }; -use lemmy_db_views::structs::SiteView; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use serde::Deserialize; use url::Url; @@ -137,18 +133,12 @@ pub(crate) async fn send_ban_from_site( moderator: Person, banned_user: Person, reason: Option, - remove_data: Option, + remove_or_restore_data: Option, ban: bool, expires: Option, context: Data, ) -> LemmyResult<()> { - let site = SiteOrCommunity::Site( - SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)? - .site - .into(), - ); + let site = SiteOrCommunity::Site(Site::read_local(&mut context.pool()).await?.into()); let expires = check_expire_time(expires)?; // if the action affects a local user, federate to other instances @@ -158,7 +148,7 @@ pub(crate) async fn send_ban_from_site( &site, &banned_user.into(), &moderator.into(), - remove_data.unwrap_or(false), + remove_or_restore_data.unwrap_or(false), reason.clone(), expires, &context, @@ -169,6 +159,7 @@ pub(crate) async fn send_ban_from_site( &site, &banned_user.into(), &moderator.into(), + remove_or_restore_data.unwrap_or(false), reason.clone(), &context, ) @@ -188,7 +179,6 @@ pub(crate) async fn send_ban_from_community( ) -> LemmyResult<()> { let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let expires = check_expire_time(data.expires)?; @@ -197,7 +187,7 @@ pub(crate) async fn send_ban_from_community( &SiteOrCommunity::Community(community), &banned_person.into(), &mod_.into(), - data.remove_data.unwrap_or(false), + data.remove_or_restore_data.unwrap_or(false), data.reason.clone(), expires, &context, @@ -208,6 +198,7 @@ pub(crate) async fn send_ban_from_community( &SiteOrCommunity::Community(community), &banned_person.into(), &mod_.into(), + data.remove_or_restore_data.unwrap_or(false), data.reason.clone(), &context, ) diff --git a/crates/apub/src/activities/block/undo_block_user.rs b/crates/apub/src/activities/block/undo_block_user.rs index 69f3eeebf..f9f6890b6 100644 --- a/crates/apub/src/activities/block/undo_block_user.rs +++ b/crates/apub/src/activities/block/undo_block_user.rs @@ -17,7 +17,10 @@ use activitypub_federation::{ protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; -use lemmy_api_common::context::LemmyContext; +use lemmy_api_common::{ + context::LemmyContext, + utils::{remove_or_restore_user_data, remove_or_restore_user_data_in_community}, +}; use lemmy_db_schema::{ source::{ activity::ActivitySendTargets, @@ -36,6 +39,7 @@ impl UndoBlockUser { target: &SiteOrCommunity, user: &ApubPerson, mod_: &ApubPerson, + restore_data: bool, reason: Option, context: &Data, ) -> LemmyResult<()> { @@ -58,6 +62,7 @@ impl UndoBlockUser { kind: UndoType::Undo, id: id.clone(), audience, + restore_data: Some(restore_data), }; let mut inboxes = ActivitySendTargets::to_inbox(user.shared_inbox_or_inbox()); @@ -114,6 +119,11 @@ impl ActivityHandler for UndoBlockUser { ) .await?; + if self.restore_data.unwrap_or(false) { + remove_or_restore_user_data(mod_person.id, blocked_person.id, false, &None, context) + .await?; + } + // write mod log let form = ModBanForm { mod_person_id: mod_person.id, @@ -132,6 +142,18 @@ impl ActivityHandler for UndoBlockUser { }; CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form).await?; + if self.restore_data.unwrap_or(false) { + remove_or_restore_user_data_in_community( + community.id, + mod_person.id, + blocked_person.id, + false, + &None, + &mut context.pool(), + ) + .await?; + } + // write to mod log let form = ModBanFromCommunityForm { mod_person_id: mod_person.id, diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index b5c4b43cf..e374d2874 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -213,14 +213,12 @@ async fn can_accept_activity_in_community( context: &Data, ) -> LemmyResult<()> { if let Some(community) = community { - if !community.local - && !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await? - { - Err(FederationError::CommunityHasNoFollowers)? - } // Local only community can't federate if community.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::CouldntFindCommunity.into()); + return Err(LemmyErrorType::NotFound.into()); + } + if !community.local { + CommunityFollower::check_has_local_followers(&mut context.pool(), community.id).await? } } Ok(()) diff --git a/crates/apub/src/activities/community/collection_add.rs b/crates/apub/src/activities/community/collection_add.rs index 4048a1469..5ab754d35 100644 --- a/crates/apub/src/activities/community/collection_add.rs +++ b/crates/apub/src/activities/community/collection_add.rs @@ -36,10 +36,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Joinable}, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl CollectionAdd { @@ -129,9 +126,7 @@ impl ActivityHandler for CollectionAdd { async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let (community, collection_type) = - Community::get_by_collection_url(&mut context.pool(), &self.target.into()) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?; match collection_type { CollectionType::Moderators => { let new_mod = ObjectId::::from(self.object) @@ -188,11 +183,9 @@ pub(crate) async fn send_add_mod_to_community( let actor: ApubPerson = actor.into(); let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let updated_mod: ApubPerson = Person::read(&mut context.pool(), updated_mod_id) .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? .into(); if added { CollectionAdd::send_add_mod(&community, &updated_mod, &actor, &context).await @@ -211,7 +204,6 @@ pub(crate) async fn send_feature_post( let post: ApubPost = post.into(); let community = Community::read(&mut context.pool(), post.community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); if featured { CollectionAdd::send_add_featured_post(&community, &post, &actor, &context).await diff --git a/crates/apub/src/activities/community/collection_remove.rs b/crates/apub/src/activities/community/collection_remove.rs index 634ca526c..90df1fd14 100644 --- a/crates/apub/src/activities/community/collection_remove.rs +++ b/crates/apub/src/activities/community/collection_remove.rs @@ -31,10 +31,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Joinable}, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl CollectionRemove { @@ -124,9 +121,7 @@ impl ActivityHandler for CollectionRemove { async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let (community, collection_type) = - Community::get_by_collection_url(&mut context.pool(), &self.target.into()) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?; match collection_type { CollectionType::Moderators => { let remove_mod = ObjectId::::from(self.object) diff --git a/crates/apub/src/activities/community/lock_page.rs b/crates/apub/src/activities/community/lock_page.rs index 322cd88c2..0d90b5bb0 100644 --- a/crates/apub/src/activities/community/lock_page.rs +++ b/crates/apub/src/activities/community/lock_page.rs @@ -32,10 +32,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; #[async_trait::async_trait] @@ -132,7 +129,6 @@ pub(crate) async fn send_lock_post( ) -> LemmyResult<()> { let community: ApubCommunity = Community::read(&mut context.pool(), post.community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let id = generate_activity_id( LockType::Lock, diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index d1bec0b75..4966add34 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -29,10 +29,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Reportable}, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl Report { @@ -70,9 +67,7 @@ impl Report { PostOrComment::Post(p) => p.creator_id, PostOrComment::Comment(c) => c.creator_id, }; - let object_creator = Person::read(&mut context.pool(), object_creator_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let object_creator = Person::read(&mut context.pool(), object_creator_id).await?; let object_creator_site: Option = Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id) .await? diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index f507b3425..48a64bd9d 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -106,8 +106,14 @@ impl ActivityHandler for UpdateCommunity { icon: Some(self.object.icon.map(|i| i.url.into())), banner: Some(self.object.image.map(|i| i.url.into())), followers_url: self.object.followers.map(Into::into), - inbox_url: Some(self.object.inbox.into()), - shared_inbox_url: Some(self.object.endpoints.map(|e| e.shared_inbox.into())), + inbox_url: Some( + self + .object + .endpoints + .map(|e| e.shared_inbox) + .unwrap_or(self.object.inbox) + .into(), + ), moderators_url: self.object.attributed_to.map(Into::into), posting_restricted_to_mods: self.object.posting_restricted_to_mods, featured_url: self.object.featured.map(Into::into), diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index 89be8d49e..0a0737151 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -42,7 +42,6 @@ use lemmy_db_schema::{ use lemmy_utils::{ error::{LemmyError, LemmyResult}, utils::mention::scrape_text_for_mentions, - LemmyErrorType, }; use url::Url; @@ -56,17 +55,11 @@ impl CreateOrUpdateNote { ) -> LemmyResult<()> { // TODO: might be helpful to add a comment method to retrieve community directly let post_id = comment.post_id; - let post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), post_id).await?; let community_id = post.community_id; - let person: ApubPerson = Person::read(&mut context.pool(), person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? - .into(); + let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into(); let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let id = generate_activity_id( @@ -160,7 +153,6 @@ impl ActivityHandler for CreateOrUpdateNote { // author likes their own comment by default let like_form = CommentLikeForm { comment_id: comment.id, - post_id: comment.post_id, person_id: comment.creator_id, score: 1, }; diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index e8bfc36e6..fb53100f6 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl CreateOrUpdatePage { @@ -66,13 +66,9 @@ impl CreateOrUpdatePage { context: Data, ) -> LemmyResult<()> { let community_id = post.community_id; - let person: ApubPerson = Person::read(&mut context.pool(), person_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? - .into(); + let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into(); let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let create_or_update = diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 97b8288f4..1ddf642b9 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -176,8 +176,8 @@ pub(in crate::activities) async fn receive_remove_action( .await?; } // TODO these need to be implemented yet, for now, return errors - DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::CouldntFindPrivateMessage)?, - DeletableObjects::Person(_) => Err(LemmyErrorType::CouldntFindPerson)?, + DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::NotFound)?, + DeletableObjects::Person(_) => Err(LemmyErrorType::NotFound)?, } Ok(()) } diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index c9d268e74..b12532087 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -39,7 +39,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use std::ops::Deref; use url::Url; @@ -87,7 +87,6 @@ pub(crate) async fn send_apub_delete_private_message( let recipient_id = pm.recipient_id; let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id) .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? .into(); let deletable = DeletableObjects::PrivateMessage(pm.into()); diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 9b66acbc0..6328bb427 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -156,8 +156,8 @@ impl UndoDelete { .await?; } // TODO these need to be implemented yet, for now, return errors - DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::CouldntFindPrivateMessage)?, - DeletableObjects::Person(_) => Err(LemmyErrorType::CouldntFindPerson)?, + DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::NotFound)?, + DeletableObjects::Person(_) => Err(LemmyErrorType::NotFound)?, } Ok(()) } diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 97227835a..02f29a1a9 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -106,7 +106,7 @@ impl ActivityHandler for Follow { UserOrCommunity::Community(c) => { // Dont allow following local-only community via federation. if c.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::CouldntFindCommunity.into()); + return Err(LemmyErrorType::NotFound.into()); } let form = CommunityFollowerForm { community_id: c.id, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 9cbe7abfd..21723c390 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -87,12 +87,7 @@ pub(crate) async fn verify_person_in_community( } let person_id = person.id; let community_id = community.id; - let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id).await?; - if is_banned { - Err(FederationError::PersonIsBannedFromCommunity)? - } else { - Ok(()) - } + CommunityPersonBanView::check(&mut context.pool(), person_id, community_id).await } /// Verify that mod action in community was performed by a moderator. @@ -106,14 +101,6 @@ pub(crate) async fn verify_mod_action( community: &Community, context: &Data, ) -> LemmyResult<()> { - let mod_ = mod_id.dereference(context).await?; - - let is_mod_or_admin = - CommunityView::is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await?; - if is_mod_or_admin { - return Ok(()); - } - // mod action comes from the same instance as the community, so it was presumably done // by an instance admin. // TODO: federate instance admin status and check it here @@ -121,7 +108,8 @@ pub(crate) async fn verify_mod_action( return Ok(()); } - Err(LemmyErrorType::NotAModerator)? + let mod_ = mod_id.dereference(context).await?; + CommunityView::check_is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await } pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { @@ -245,9 +233,7 @@ pub async fn match_outgoing_activities( CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Update, context).await } DeletePost(post, person, data) => { - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; send_apub_delete_in_community( person, community, @@ -264,9 +250,7 @@ pub async fn match_outgoing_activities( reason, removed, } => { - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; send_apub_delete_in_community( moderator, community, @@ -352,7 +336,7 @@ pub async fn match_outgoing_activities( moderator, banned_user, reason, - remove_data, + remove_or_restore_data, ban, expires, } => { @@ -360,7 +344,7 @@ pub async fn match_outgoing_activities( moderator, banned_user, reason, - remove_data, + remove_or_restore_data, ban, expires, context, diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 3e59cb7d0..7c39b2246 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -62,7 +62,6 @@ async fn vote_comment( let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, - post_id: comment.post_id, person_id: actor.id, score: vote_type.into(), }; diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 324c8b300..1cdc81952 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -18,7 +18,7 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use lemmy_api_common::{context::LemmyContext, utils::check_bot_account}; -use lemmy_db_schema::source::local_site::LocalSite; +use lemmy_db_schema::{source::local_site::LocalSite, FederationMode}; use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; @@ -68,12 +68,22 @@ impl ActivityHandler for Vote { check_bot_account(&actor.0)?; - let enable_downvotes = LocalSite::read(&mut context.pool()) + // Check for enabled federation votes + let local_site = LocalSite::read(&mut context.pool()) .await - .map(|l| l.enable_downvotes) - .unwrap_or(true); - if self.kind == VoteType::Dislike && !enable_downvotes { - // If this is a downvote but downvotes are ignored, only undo any existing vote + .unwrap_or_default(); + + let (downvote_setting, upvote_setting) = match object { + PostOrComment::Post(_) => (local_site.post_downvotes, local_site.post_upvotes), + PostOrComment::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes), + }; + + // Don't allow dislikes for either disabled, or local only votes + let downvote_fail = self.kind == VoteType::Dislike && downvote_setting != FederationMode::All; + let upvote_fail = self.kind == VoteType::Like && upvote_setting != FederationMode::All; + + if downvote_fail || upvote_fail { + // If this is a rejection, undo the vote match object { PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await, PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await, diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 2d1fac449..9262236d8 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -117,13 +117,12 @@ impl InCommunity for AnnouncableActivities { CollectionRemove(a) => a.community(context).await, LockPost(a) => a.community(context).await, UndoLockPost(a) => a.community(context).await, - Page(_) => Err(LemmyErrorType::CouldntFindPost.into()), + Page(_) => Err(LemmyErrorType::NotFound.into()), } } } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index 12d18110e..3e7a2f4eb 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -1,3 +1,4 @@ +use super::comment_sort_type_with_default; use crate::{ api::listing_type_with_default, fetcher::resolve_actor_identifier, @@ -11,10 +12,13 @@ use lemmy_api_common::{ utils::check_private_instance, }; use lemmy_db_schema::{ - source::{comment::Comment, community::Community, local_site::LocalSite}, + source::{comment::Comment, community::Community}, traits::Crud, }; -use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView}; +use lemmy_db_views::{ + comment_view::CommentQuery, + structs::{LocalUserView, SiteView}, +}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] @@ -23,8 +27,8 @@ pub async fn list_comments( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; - check_private_instance(&local_user_view, &local_site)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; + check_private_instance(&local_user_view, &site_view.local_site)?; let community_id = if let Some(name) = &data.community_name { Some( @@ -35,7 +39,12 @@ pub async fn list_comments( } else { data.community_id }; - let sort = data.sort; + let local_user_ref = local_user_view.as_ref().map(|u| &u.local_user); + let sort = Some(comment_sort_type_with_default( + data.sort, + local_user_ref, + &site_view.local_site, + )); let max_depth = data.max_depth; let saved_only = data.saved_only; @@ -52,18 +61,13 @@ pub async fn list_comments( let listing_type = Some(listing_type_with_default( data.type_, local_user_view.as_ref().map(|u| &u.local_user), - &local_site, + &site_view.local_site, community_id, )); // If a parent_id is given, fetch the comment to get the path let parent_path = if let Some(parent_id) = parent_id { - Some( - Comment::read(&mut context.pool(), parent_id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)? - .path, - ) + Some(Comment::read(&mut context.pool(), parent_id).await?.path) } else { None }; @@ -87,7 +91,7 @@ pub async fn list_comments( limit, ..Default::default() } - .list(&mut context.pool()) + .list(&site_view.site, &mut context.pool()) .await .with_lemmy_type(LemmyErrorType::CouldntGetComments)?; diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index 7ceafed8d..d75a82d3b 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -1,5 +1,5 @@ use crate::{ - api::{listing_type_with_default, sort_type_with_default}, + api::{listing_type_with_default, post_sort_type_with_default}, fetcher::resolve_actor_identifier, objects::community::ApubCommunity, }; @@ -8,14 +8,14 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPosts, GetPostsResponse}, - utils::check_private_instance, + utils::{check_conflicting_like_filters, check_private_instance}, }; use lemmy_db_schema::source::community::Community; use lemmy_db_views::{ post_view::PostQuery, structs::{LocalUserView, PaginationCursor, SiteView}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn list_posts( @@ -23,9 +23,7 @@ pub async fn list_posts( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let local_site = SiteView::read_local(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -47,9 +45,7 @@ pub async fn list_posts( let liked_only = data.liked_only; let disliked_only = data.disliked_only; - if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() { - return Err(LemmyError::from(LemmyErrorType::ContradictingFilters)); - } + check_conflicting_like_filters(liked_only, disliked_only)?; let local_user = local_user_view.as_ref().map(|u| &u.local_user); let listing_type = Some(listing_type_with_default( @@ -59,7 +55,7 @@ pub async fn list_posts( community_id, )); - let sort = Some(sort_type_with_default( + let sort = Some(post_sort_type_with_default( data.sort, local_user, &local_site.local_site, diff --git a/crates/apub/src/api/mod.rs b/crates/apub/src/api/mod.rs index dab2ace06..580be3228 100644 --- a/crates/apub/src/api/mod.rs +++ b/crates/apub/src/api/mod.rs @@ -1,8 +1,9 @@ use lemmy_db_schema::{ newtypes::CommunityId, source::{local_site::LocalSite, local_user::LocalUser}, + CommentSortType, ListingType, - SortType, + PostSortType, }; pub mod list_comments; @@ -33,16 +34,30 @@ fn listing_type_with_default( } } -/// Returns a default instance-level sort type, if none is given by the user. +/// Returns a default instance-level post sort type, if none is given by the user. /// Order is type, local user default, then site default. -fn sort_type_with_default( - type_: Option, +fn post_sort_type_with_default( + type_: Option, local_user: Option<&LocalUser>, local_site: &LocalSite, -) -> SortType { +) -> PostSortType { type_.unwrap_or( local_user - .map(|u| u.default_sort_type) - .unwrap_or(local_site.default_sort_type), + .map(|u| u.default_post_sort_type) + .unwrap_or(local_site.default_post_sort_type), + ) +} + +/// Returns a default instance-level comment sort type, if none is given by the user. +/// Order is type, local user default, then site default. +fn comment_sort_type_with_default( + type_: Option, + local_user: Option<&LocalUser>, + local_site: &LocalSite, +) -> CommentSortType { + type_.unwrap_or( + local_user + .map(|u| u.default_comment_sort_type) + .unwrap_or(local_site.default_comment_sort_type), ) } diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs index 62fd6ec0b..f94769158 100644 --- a/crates/apub/src/api/read_community.rs +++ b/crates/apub/src/api/read_community.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::source::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn get_community( @@ -36,8 +36,7 @@ pub async fn get_community( None => { let name = data.name.clone().unwrap_or_else(|| "main".to_string()); resolve_actor_identifier::(&name, &context, &local_user_view, true) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)? + .await? .id } }; @@ -56,12 +55,9 @@ pub async fn get_community( local_user, is_mod_or_admin, ) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .await?; - let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; let site = read_site_for_actor(community_view.community.actor_id.clone(), &context).await?; diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index d61f6d9c5..fac68cd63 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -13,7 +13,7 @@ use lemmy_db_views::{ structs::{LocalUserView, SiteView}, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; -use lemmy_utils::error::{LemmyErrorExt2, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn read_person( @@ -26,9 +26,7 @@ pub async fn read_person( Err(LemmyErrorType::NoIdGiven)? } - let local_site = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let local_site = SiteView::read_local(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -37,20 +35,17 @@ pub async fn read_person( None => { if let Some(username) = &data.username { resolve_actor_identifier::(username, &context, &local_user_view, true) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPerson)? + .await? .id } else { - Err(LemmyErrorType::CouldntFindPerson)? + Err(LemmyErrorType::NotFound)? } } }; // You don't need to return settings for the user, since this comes back with GetSite // `my_user` - let person_view = PersonView::read(&mut context.pool(), person_details_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let person_view = PersonView::read(&mut context.pool(), person_details_id).await?; let sort = data.sort; let page = data.page; @@ -90,7 +85,7 @@ pub async fn read_person( creator_id, ..Default::default() } - .list(&mut context.pool()) + .list(&local_site.site, &mut context.pool()) .await?; let moderates = CommunityModeratorView::for_person( diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index 4c592aeeb..d9d50e69e 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -1,10 +1,10 @@ use crate::fetcher::{ + post_or_comment::PostOrComment, search::{search_query_to_object_id, search_query_to_object_id_local, SearchableObjects}, user_or_community::UserOrCommunity, }; use activitypub_federation::config::Data; use actix_web::web::{Json, Query}; -use diesel::NotFound; use lemmy_api_common::{ context::LemmyContext, site::{ResolveObject, ResolveObjectResponse}, @@ -34,11 +34,11 @@ pub async fn resolve_object( // user isn't authenticated only allow a local search. search_query_to_object_id_local(&data.q, &context).await } - .with_lemmy_type(LemmyErrorType::CouldntFindObject)?; + .with_lemmy_type(LemmyErrorType::NotFound)?; convert_response(res, local_user_view, &mut context.pool()) .await - .with_lemmy_type(LemmyErrorType::CouldntFindObject) + .with_lemmy_type(LemmyErrorType::NotFound) } async fn convert_response( @@ -46,51 +46,145 @@ async fn convert_response( local_user_view: Option, pool: &mut DbPool<'_>, ) -> LemmyResult> { - use SearchableObjects::*; - let removed_or_deleted; let mut res = ResolveObjectResponse::default(); let local_user = local_user_view.map(|l| l.local_user); + let is_admin = local_user.clone().map(|l| l.admin).unwrap_or_default(); match object { - Post(p) => { - removed_or_deleted = p.deleted || p.removed; - res.post = Some( - PostView::read(pool, p.id, local_user.as_ref(), false) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?, - ) - } - Comment(c) => { - removed_or_deleted = c.deleted || c.removed; - res.comment = Some( - CommentView::read(pool, c.id, local_user.as_ref()) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?, - ) - } - PersonOrCommunity(p) => match *p { - UserOrCommunity::User(u) => { - removed_or_deleted = u.deleted; - res.person = Some( - PersonView::read(pool, u.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?, - ) + SearchableObjects::PostOrComment(pc) => match *pc { + PostOrComment::Post(p) => { + res.post = Some(PostView::read(pool, p.id, local_user.as_ref(), is_admin).await?) } + PostOrComment::Comment(c) => { + res.comment = Some(CommentView::read(pool, c.id, local_user.as_ref()).await?) + } + }, + SearchableObjects::PersonOrCommunity(pc) => match *pc { + UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id).await?), UserOrCommunity::Community(c) => { - removed_or_deleted = c.deleted || c.removed; - res.community = Some( - CommunityView::read(pool, c.id, local_user.as_ref(), false) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?, - ) + res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?) } }, }; - // if the object was deleted from database, dont return it - if removed_or_deleted { - Err(NotFound {}.into()) - } else { - Ok(Json(res)) + + Ok(Json(res)) +} + +#[cfg(test)] +mod tests { + use crate::api::resolve_object::resolve_object; + use actix_web::web::Query; + use lemmy_api_common::{context::LemmyContext, site::ResolveObject}; + use lemmy_db_schema::{ + source::{ + community::{Community, CommunityInsertForm}, + instance::Instance, + local_site::{LocalSite, LocalSiteInsertForm}, + post::{Post, PostInsertForm, PostUpdateForm}, + site::{Site, SiteInsertForm}, + }, + traits::Crud, + }; + use lemmy_db_views::structs::LocalUserView; + use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use serial_test::serial; + + #[tokio::test] + #[serial] + #[expect(clippy::unwrap_used)] + async fn test_object_visibility() -> LemmyResult<()> { + let context = LemmyContext::init_test_context().await; + let pool = &mut context.pool(); + + let name = "test_local_user_name"; + let bio = "test_local_user_bio"; + + let creator = LocalUserView::create_test_user(pool, name, bio, false).await?; + let regular_user = LocalUserView::create_test_user(pool, name, bio, false).await?; + let admin_user = LocalUserView::create_test_user(pool, name, bio, true).await?; + + let instance_id = creator.person.instance_id; + let site_form = SiteInsertForm::new("test site".to_string(), instance_id); + let site = Site::create(pool, &site_form).await?; + + let local_site_form = LocalSiteInsertForm { + site_setup: Some(true), + private_instance: Some(false), + ..LocalSiteInsertForm::new(site.id) + }; + LocalSite::create(pool, &local_site_form).await?; + + let community = Community::create( + pool, + &CommunityInsertForm::new( + instance_id, + "test".to_string(), + "test".to_string(), + "pubkey".to_string(), + ), + ) + .await?; + + let post_insert_form = PostInsertForm::new("Test".to_string(), creator.person.id, community.id); + let post = Post::create(pool, &post_insert_form).await?; + + let query = format!("q={}", post.ap_id).to_string(); + let query: Query = Query::from_query(&query)?; + + // Objects should be resolvable without authentication + let res = resolve_object(query.clone(), context.reset_request_count(), None).await?; + assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id); + // Objects should be resolvable by regular users + let res = resolve_object( + query.clone(), + context.reset_request_count(), + Some(regular_user.clone()), + ) + .await?; + assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id); + // Objects should be resolvable by admins + let res = resolve_object( + query.clone(), + context.reset_request_count(), + Some(admin_user.clone()), + ) + .await?; + assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id); + + Post::update( + pool, + post.id, + &PostUpdateForm { + deleted: Some(true), + ..Default::default() + }, + ) + .await?; + + // Deleted objects should not be resolvable without authentication + let res = resolve_object(query.clone(), context.reset_request_count(), None).await; + assert!(res.is_err_and(|e| e.error_type == LemmyErrorType::NotFound)); + // Deleted objects should not be resolvable by regular users + let res = resolve_object( + query.clone(), + context.reset_request_count(), + Some(regular_user.clone()), + ) + .await; + assert!(res.is_err_and(|e| e.error_type == LemmyErrorType::NotFound)); + // Deleted objects should be resolvable by admins + let res = resolve_object( + query.clone(), + context.reset_request_count(), + Some(admin_user.clone()), + ) + .await?; + assert_eq!(res.post.as_ref().unwrap().post.ap_id, post.ap_id); + + LocalSite::delete(pool).await?; + Site::delete(pool, site.id).await?; + Instance::delete(pool, instance_id).await?; + + Ok(()) } } diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index a048b64a7..cdc9bc55e 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -4,7 +4,7 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, site::{Search, SearchResponse}, - utils::{check_private_instance, is_admin}, + utils::{check_conflicting_like_filters, check_private_instance, is_admin}, }; use lemmy_db_schema::{source::community::Community, utils::post_to_comment_sort_type, SearchType}; use lemmy_db_views::{ @@ -12,8 +12,12 @@ use lemmy_db_views::{ post_view::PostQuery, structs::{LocalUserView, SiteView}, }; -use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_db_views_actor::{ + community_view::CommunityQuery, + person_view::PersonQuery, + structs::CommunitySortType, +}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn search( @@ -21,9 +25,7 @@ pub async fn search( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let local_site = SiteView::read_local(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -39,167 +41,136 @@ pub async fn search( // TODO no clean / non-nsfw searching rn - let q = data.q.clone(); - let page = data.page; - let limit = data.limit; - let sort = data.sort; - let listing_type = data.listing_type; - let search_type = data.type_.unwrap_or(SearchType::All); - let community_id = if let Some(name) = &data.community_name { + let Query(Search { + q, + community_id, + community_name, + creator_id, + type_, + sort, + listing_type, + page, + limit, + title_only, + post_url_only, + saved_only, + liked_only, + disliked_only, + }) = data; + + let q = q.clone(); + let search_type = type_.unwrap_or(SearchType::All); + let community_id = if let Some(name) = &community_name { Some( resolve_actor_identifier::(name, &context, &local_user_view, false) .await?, ) .map(|c| c.id) } else { - data.community_id + community_id }; - let creator_id = data.creator_id; let local_user = local_user_view.as_ref().map(|l| &l.local_user); + check_conflicting_like_filters(liked_only, disliked_only)?; + + let posts_query = PostQuery { + sort, + listing_type, + community_id, + creator_id, + local_user, + search_term: Some(q.clone()), + page, + limit, + title_only, + url_only: post_url_only, + liked_only, + disliked_only, + saved_only, + ..Default::default() + }; + + let comment_query = CommentQuery { + sort: sort.map(post_to_comment_sort_type), + listing_type, + search_term: Some(q.clone()), + community_id, + creator_id, + local_user, + page, + limit, + liked_only, + disliked_only, + saved_only, + ..Default::default() + }; + + let community_query = CommunityQuery { + sort: sort.map(CommunitySortType::from), + listing_type, + search_term: Some(q.clone()), + title_only, + local_user, + is_mod_or_admin: is_admin, + page, + limit, + ..Default::default() + }; + + let person_query = PersonQuery { + sort, + search_term: Some(q.clone()), + listing_type, + page, + limit, + }; + match search_type { SearchType::Posts => { - posts = PostQuery { - sort: (sort), - listing_type: (listing_type), - community_id: (community_id), - creator_id: (creator_id), - local_user, - search_term: (Some(q)), - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await?; + posts = posts_query + .list(&local_site.site, &mut context.pool()) + .await?; } SearchType::Comments => { - comments = CommentQuery { - sort: (sort.map(post_to_comment_sort_type)), - listing_type: (listing_type), - search_term: (Some(q)), - community_id: (community_id), - creator_id: (creator_id), - local_user, - page: (page), - limit: (limit), - ..Default::default() - } - .list(&mut context.pool()) - .await?; + comments = comment_query + .list(&local_site.site, &mut context.pool()) + .await?; } SearchType::Communities => { - communities = CommunityQuery { - sort: (sort), - listing_type: (listing_type), - search_term: (Some(q)), - local_user, - is_mod_or_admin: (is_admin), - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await?; + communities = community_query + .list(&local_site.site, &mut context.pool()) + .await?; } SearchType::Users => { - users = PersonQuery { - sort, - search_term: (Some(q)), - listing_type: (listing_type), - page: (page), - limit: (limit), - } - .list(&mut context.pool()) - .await?; + users = person_query.list(&mut context.pool()).await?; } SearchType::All => { // If the community or creator is included, dont search communities or users let community_or_creator_included = - data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); + community_id.is_some() || community_name.is_some() || creator_id.is_some(); - let q = data.q.clone(); + posts = posts_query + .list(&local_site.site, &mut context.pool()) + .await?; - posts = PostQuery { - sort: (sort), - listing_type: (listing_type), - community_id: (community_id), - creator_id: (creator_id), - local_user, - search_term: (Some(q)), - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await?; - - let q = data.q.clone(); - - comments = CommentQuery { - sort: (sort.map(post_to_comment_sort_type)), - listing_type: (listing_type), - search_term: (Some(q)), - community_id: (community_id), - creator_id: (creator_id), - local_user, - page: (page), - limit: (limit), - ..Default::default() - } - .list(&mut context.pool()) - .await?; - - let q = data.q.clone(); + comments = comment_query + .list(&local_site.site, &mut context.pool()) + .await?; communities = if community_or_creator_included { vec![] } else { - CommunityQuery { - sort: (sort), - listing_type: (listing_type), - search_term: (Some(q)), - local_user, - is_mod_or_admin: (is_admin), - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await? + community_query + .list(&local_site.site, &mut context.pool()) + .await? }; - let q = data.q.clone(); - users = if community_or_creator_included { vec![] } else { - PersonQuery { - sort, - search_term: (Some(q)), - listing_type: (listing_type), - page: (page), - limit: (limit), - } - .list(&mut context.pool()) - .await? + person_query.list(&mut context.pool()).await? }; } - SearchType::Url => { - posts = PostQuery { - sort: (sort), - listing_type: (listing_type), - community_id: (community_id), - creator_id: (creator_id), - url_search: (Some(q)), - local_user, - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await?; - } }; // Return the jwt diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index fdf8dc2ad..2e075c202 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -103,18 +103,22 @@ pub async fn import_settings( context: Data, ) -> LemmyResult> { let person_form = PersonUpdateForm { - display_name: Some(data.display_name.clone()), - bio: Some(data.bio.clone()), - matrix_user_id: Some(data.matrix_id.clone()), + display_name: data.display_name.clone().map(Some), + bio: data.bio.clone().map(Some), + matrix_user_id: data.bio.clone().map(Some), bot_account: data.bot_account, ..Default::default() }; - Person::update(&mut context.pool(), local_user_view.person.id, &person_form).await?; + // ignore error in case form is empty + Person::update(&mut context.pool(), local_user_view.person.id, &person_form) + .await + .ok(); let local_user_form = LocalUserUpdateForm { show_nsfw: data.settings.as_ref().map(|s| s.show_nsfw), theme: data.settings.clone().map(|s| s.theme.clone()), - default_sort_type: data.settings.as_ref().map(|s| s.default_sort_type), + default_post_sort_type: data.settings.as_ref().map(|s| s.default_post_sort_type), + default_comment_sort_type: data.settings.as_ref().map(|s| s.default_comment_sort_type), default_listing_type: data.settings.as_ref().map(|s| s.default_listing_type), interface_language: data.settings.clone().map(|s| s.interface_language), show_avatars: data.settings.as_ref().map(|s| s.show_avatars), @@ -126,7 +130,6 @@ pub async fn import_settings( show_read_posts: data.settings.as_ref().map(|s| s.show_read_posts), open_links_in_new_tab: data.settings.as_ref().map(|s| s.open_links_in_new_tab), blur_nsfw: data.settings.as_ref().map(|s| s.blur_nsfw), - auto_expand: data.settings.as_ref().map(|s| s.auto_expand), infinite_scroll_enabled: data.settings.as_ref().map(|s| s.infinite_scroll_enabled), post_listing_mode: data.settings.as_ref().map(|s| s.post_listing_mode), ..Default::default() @@ -307,19 +310,17 @@ where }); Ok(failed_items.into_iter().join(",")) } -#[cfg(test)] -#[allow(clippy::indexing_slicing)] -mod tests { - use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup}; - use activitypub_federation::config::Data; +#[cfg(test)] +#[expect(clippy::indexing_slicing)] +pub(crate) mod tests { + use crate::api::user_settings_backup::{export_settings, import_settings}; + use actix_web::web::Json; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm}, - instance::Instance, - local_user::{LocalUser, LocalUserInsertForm}, - person::{Person, PersonInsertForm}, + local_user::LocalUser, }, traits::{Crud, Followable}, }; @@ -331,62 +332,39 @@ mod tests { use std::time::Duration; use tokio::time::sleep; - async fn create_user( - name: String, - bio: Option, - context: &Data, - ) -> LemmyResult { - let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?; - 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::test_form(person.id); - let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?; - - Ok( - LocalUserView::read(&mut context.pool(), local_user.id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?, - ) - } - #[tokio::test] #[serial] async fn test_settings_export_import() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; + let pool = &mut context.pool(); - let export_user = - create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?; + let export_user = LocalUserView::create_test_user(pool, "hanna", "my bio", false).await?; - let community_form = CommunityInsertForm::builder() - .name("testcom".to_string()) - .title("testcom".to_string()) - .instance_id(export_user.person.instance_id) - .build(); - let community = Community::create(&mut context.pool(), &community_form).await?; + let community_form = CommunityInsertForm::new( + export_user.person.instance_id, + "testcom".to_string(), + "testcom".to_string(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; let follower_form = CommunityFollowerForm { community_id: community.id, person_id: export_user.person.id, pending: false, }; - CommunityFollower::follow(&mut context.pool(), &follower_form).await?; + CommunityFollower::follow(pool, &follower_form).await?; let backup = export_settings(export_user.clone(), context.reset_request_count()).await?; - let import_user = create_user("charles".to_string(), None, &context).await?; + let import_user = + LocalUserView::create_test_user(pool, "charles", "charles bio", false).await?; import_settings(backup, import_user.clone(), context.reset_request_count()).await?; // wait for background task to finish sleep(Duration::from_millis(1000)).await; - let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let import_user_updated = LocalUserView::read(pool, import_user.local_user.id).await?; assert_eq!( export_user.person.display_name, @@ -394,51 +372,12 @@ mod tests { ); assert_eq!(export_user.person.bio, import_user_updated.person.bio); - let follows = - CommunityFollowerView::for_person(&mut context.pool(), import_user.person.id).await?; + let follows = CommunityFollowerView::for_person(pool, import_user.person.id).await?; assert_eq!(follows.len(), 1); assert_eq!(follows[0].community.actor_id, community.actor_id); - LocalUser::delete(&mut context.pool(), export_user.local_user.id).await?; - LocalUser::delete(&mut context.pool(), import_user.local_user.id).await?; - Ok(()) - } - - #[tokio::test] - #[serial] - async fn test_settings_partial_import() -> LemmyResult<()> { - let context = LemmyContext::init_test_context().await; - - let export_user = - create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?; - - let community_form = CommunityInsertForm::builder() - .name("testcom".to_string()) - .title("testcom".to_string()) - .instance_id(export_user.person.instance_id) - .build(); - let community = Community::create(&mut context.pool(), &community_form).await?; - let follower_form = CommunityFollowerForm { - community_id: community.id, - person_id: export_user.person.id, - pending: false, - }; - CommunityFollower::follow(&mut context.pool(), &follower_form).await?; - - let backup = export_settings(export_user.clone(), context.reset_request_count()).await?; - - let import_user = create_user("charles".to_string(), None, &context).await?; - - let backup2 = UserSettingsBackup { - followed_communities: backup.followed_communities.clone(), - ..Default::default() - }; - import_settings( - actix_web::web::Json(backup2), - import_user.clone(), - context.reset_request_count(), - ) - .await?; + LocalUser::delete(pool, export_user.local_user.id).await?; + LocalUser::delete(pool, import_user.local_user.id).await?; Ok(()) } @@ -446,9 +385,9 @@ mod tests { #[serial] async fn disallow_large_backup() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; + let pool = &mut context.pool(); - let export_user = - create_user("hanna".to_string(), Some("my bio".to_string()), &context).await?; + let export_user = LocalUserView::create_test_user(pool, "harry", "harry bio", false).await?; let mut backup = export_settings(export_user.clone(), context.reset_request_count()).await?; @@ -463,7 +402,7 @@ mod tests { backup.saved_comments.push("http://example4.com".parse()?); } - let import_user = create_user("charles".to_string(), None, &context).await?; + let import_user = LocalUserView::create_test_user(pool, "sally", "sally bio", false).await?; let imported = import_settings(backup, import_user.clone(), context.reset_request_count()).await; @@ -473,8 +412,36 @@ mod tests { Some(LemmyErrorType::TooManyItems) ); - LocalUser::delete(&mut context.pool(), export_user.local_user.id).await?; - LocalUser::delete(&mut context.pool(), import_user.local_user.id).await?; + LocalUser::delete(pool, export_user.local_user.id).await?; + LocalUser::delete(pool, import_user.local_user.id).await?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn import_partial_backup() -> LemmyResult<()> { + let context = LemmyContext::init_test_context().await; + let pool = &mut context.pool(); + + let import_user = LocalUserView::create_test_user(pool, "larry", "larry bio", false).await?; + + let backup = + serde_json::from_str("{\"bot_account\": true, \"settings\": {\"theme\": \"my_theme\"}}")?; + import_settings( + Json(backup), + import_user.clone(), + context.reset_request_count(), + ) + .await?; + + let import_user_updated = LocalUserView::read(pool, import_user.local_user.id).await?; + // mark as bot account + assert!(import_user_updated.person.bot_account); + // dont remove existing bio + assert_eq!(import_user.person.bio, import_user_updated.person.bio); + // local_user can be deserialized without id/person_id fields + assert_eq!("my_theme", import_user_updated.local_user.theme); + Ok(()) } } diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 8e5419c7e..c7b925f97 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -98,7 +98,7 @@ impl Collection for ApubCommunityModerators { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs index 8aca82d38..01199bc2b 100644 --- a/crates/apub/src/collections/community_outbox.rs +++ b/crates/apub/src/collections/community_outbox.rs @@ -18,12 +18,9 @@ use activitypub_federation::{ }; use futures::future::join_all; use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url}; -use lemmy_db_schema::{utils::FETCH_LIMIT_MAX, SortType}; -use lemmy_db_views::{post_view::PostQuery, structs::SiteView}; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_db_schema::{source::site::Site, utils::FETCH_LIMIT_MAX, PostSortType}; +use lemmy_db_views::post_view::PostQuery; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; #[derive(Clone, Debug)] @@ -38,14 +35,11 @@ impl Collection for ApubCommunityOutbox { #[tracing::instrument(skip_all)] async fn read_local(owner: &Self::Owner, data: &Data) -> LemmyResult { - let site = SiteView::read_local(&mut data.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)? - .site; + let site = Site::read_local(&mut data.pool()).await?; let post_views = PostQuery { community_id: Some(owner.id), - sort: Some(SortType::New), + sort: Some(PostSortType::New), limit: Some(FETCH_LIMIT_MAX), ..Default::default() } diff --git a/crates/apub/src/fetcher/markdown_links.rs b/crates/apub/src/fetcher/markdown_links.rs new file mode 100644 index 000000000..d83aae515 --- /dev/null +++ b/crates/apub/src/fetcher/markdown_links.rs @@ -0,0 +1,192 @@ +use super::{search::SearchableObjects, user_or_community::UserOrCommunity}; +use crate::fetcher::post_or_comment::PostOrComment; +use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; +use lemmy_api_common::{ + context::LemmyContext, + utils::{generate_local_apub_endpoint, EndpointType}, +}; +use lemmy_db_schema::{newtypes::InstanceId, source::instance::Instance}; +use lemmy_utils::{ + error::LemmyResult, + utils::markdown::image_links::{markdown_find_links, markdown_handle_title}, +}; +use url::Url; + +pub async fn markdown_rewrite_remote_links_opt( + src: Option, + context: &Data, +) -> Option { + match src { + Some(t) => Some(markdown_rewrite_remote_links(t, context).await), + None => None, + } +} + +/// Goes through all remote markdown links and attempts to resolve them as Activitypub objects. +/// If successful, the link is rewritten to a local link, so it can be viewed without leaving the +/// local instance. +/// +/// As it relies on ObjectId::dereference, it can only be used for incoming federated objects, not +/// for the API. +pub async fn markdown_rewrite_remote_links( + mut src: String, + context: &Data, +) -> String { + let links_offsets = markdown_find_links(&src); + + // Go through the collected links in reverse order + for (start, end) in links_offsets.into_iter().rev() { + let (url, extra) = markdown_handle_title(&src, start, end); + + if let Some(local_url) = to_local_url(url, context).await { + let mut local_url = local_url.to_string(); + // restore title + if let Some(extra) = extra { + local_url = format!("{local_url} {extra}"); + } + src.replace_range(start..end, local_url.as_str()); + } + } + + src +} + +pub(crate) async fn to_local_url(url: &str, context: &Data) -> Option { + let local_domain = &context.settings().get_protocol_and_hostname(); + let object_id = ObjectId::::parse(url).ok()?; + if object_id.inner().domain() == Some(local_domain) { + return None; + } + let dereferenced = object_id.dereference(context).await.ok()?; + match dereferenced { + SearchableObjects::PostOrComment(pc) => match *pc { + PostOrComment::Post(post) => { + generate_local_apub_endpoint(EndpointType::Post, &post.id.to_string(), local_domain) + } + PostOrComment::Comment(comment) => { + generate_local_apub_endpoint(EndpointType::Comment, &comment.id.to_string(), local_domain) + } + } + .ok() + .map(Into::into), + SearchableObjects::PersonOrCommunity(pc) => match *pc { + UserOrCommunity::User(user) => { + format_actor_url(&user.name, "u", user.instance_id, context).await + } + UserOrCommunity::Community(community) => { + format_actor_url(&community.name, "c", community.instance_id, context).await + } + } + .ok(), + } +} + +async fn format_actor_url( + name: &str, + kind: &str, + instance_id: InstanceId, + context: &LemmyContext, +) -> LemmyResult { + let local_protocol_and_hostname = context.settings().get_protocol_and_hostname(); + let local_hostname = &context.settings().hostname; + let instance = Instance::read(&mut context.pool(), instance_id).await?; + let url = if &instance.domain != local_hostname { + format!( + "{local_protocol_and_hostname}/{kind}/{name}@{}", + instance.domain + ) + } else { + format!("{local_protocol_and_hostname}/{kind}/{name}") + }; + Ok(Url::parse(&url)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use lemmy_db_schema::{ + source::{ + community::{Community, CommunityInsertForm}, + post::{Post, PostInsertForm}, + }, + traits::Crud, + }; + use lemmy_db_views::structs::LocalUserView; + use pretty_assertions::assert_eq; + use serial_test::serial; + + #[serial] + #[tokio::test] + async fn test_markdown_rewrite_remote_links() -> LemmyResult<()> { + let context = LemmyContext::init_test_context().await; + let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?; + let community = Community::create( + &mut context.pool(), + &CommunityInsertForm::new( + instance.id, + "my_community".to_string(), + "My Community".to_string(), + "pubkey".to_string(), + ), + ) + .await?; + let user = + LocalUserView::create_test_user(&mut context.pool(), "garda", "garda bio", false).await?; + + // insert a remote post which is already fetched + let post_form = PostInsertForm { + ap_id: Some(Url::parse("https://example.com/post/123")?.into()), + ..PostInsertForm::new("My post".to_string(), user.person.id, community.id) + }; + let post = Post::create(&mut context.pool(), &post_form).await?; + let markdown_local_post_url = format!("[link](https://lemmy-alpha/post/{})", post.id); + + let tests: Vec<_> = vec![ + ( + "rewrite remote post link", + format!("[link]({})", post.ap_id), + markdown_local_post_url.as_ref(), + ), + ( + "rewrite community link", + format!("[link]({})", community.actor_id), + "[link](https://lemmy-alpha/c/my_community@example.com)", + ), + ( + "dont rewrite local post link", + "[link](https://lemmy-alpha/post/2)".to_string(), + "[link](https://lemmy-alpha/post/2)", + ), + ( + "dont rewrite local community link", + "[link](https://lemmy-alpha/c/test)".to_string(), + "[link](https://lemmy-alpha/c/test)", + ), + ( + "dont rewrite non-fediverse link", + "[link](https://example.com/)".to_string(), + "[link](https://example.com/)", + ), + ( + "dont rewrite invalid url", + "[link](example-com)".to_string(), + "[link](example-com)", + ), + ]; + + let context = LemmyContext::init_test_context().await; + for (msg, input, expected) in &tests { + let result = markdown_rewrite_remote_links(input.to_string(), &context).await; + + assert_eq!( + &result, expected, + "Testing {}, with original input '{}'", + msg, input + ); + } + + Instance::delete(&mut context.pool(), instance.id).await?; + + Ok(()) + } +} diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index 68fc07d30..29202004f 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -10,6 +10,7 @@ use lemmy_db_schema::traits::ApubActor; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::{LemmyError, LemmyResult}; +pub(crate) mod markdown_links; pub mod post_or_comment; pub mod search; pub mod site_or_community_or_user; diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index e352e1257..be48e8ebd 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -12,10 +12,7 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{ - error::{LemmyError, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyError, LemmyResult}; use serde::Deserialize; use url::Url; @@ -29,7 +26,7 @@ pub enum PostOrComment { #[serde(untagged)] pub enum PageOrNote { Page(Box), - Note(Note), + Note(Box), } #[async_trait::async_trait] @@ -64,7 +61,7 @@ impl Object for PostOrComment { async fn into_json(self, data: &Data) -> LemmyResult { Ok(match self { PostOrComment::Post(p) => PageOrNote::Page(Box::new(p.into_json(data).await?)), - PostOrComment::Comment(c) => PageOrNote::Note(c.into_json(data).await?), + PostOrComment::Comment(c) => PageOrNote::Note(Box::new(c.into_json(data).await?)), }) } @@ -84,7 +81,7 @@ impl Object for PostOrComment { async fn from_json(apub: PageOrNote, context: &Data) -> LemmyResult { Ok(match apub { PageOrNote::Page(p) => PostOrComment::Post(ApubPost::from_json(*p, context).await?), - PageOrNote::Note(n) => PostOrComment::Comment(ApubComment::from_json(n, context).await?), + PageOrNote::Note(n) => PostOrComment::Comment(ApubComment::from_json(*n, context).await?), }) } } @@ -97,15 +94,9 @@ impl InCommunity for PostOrComment { PostOrComment::Comment(c) => { Post::read(&mut context.pool(), c.post_id) .await? - .ok_or(LemmyErrorType::CouldntFindPost)? .community_id } }; - Ok( - Community::read(&mut context.pool(), cid) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? - .into(), - ) + Ok(Community::read(&mut context.pool(), cid).await?.into()) } } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 76c284820..e8c029106 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -1,8 +1,5 @@ -use crate::{ - fetcher::user_or_community::{PersonOrGroup, UserOrCommunity}, - objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::objects::{note::Note, page::Page}, -}; +use super::post_or_comment::{PageOrNote, PostOrComment}; +use crate::fetcher::user_or_community::{PersonOrGroup, UserOrCommunity}; use activitypub_federation::{ config::Data, fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, @@ -54,16 +51,14 @@ pub(crate) async fn search_query_to_object_id_local( /// The types of ActivityPub objects that can be fetched directly by searching for their ID. #[derive(Debug)] pub(crate) enum SearchableObjects { - Post(ApubPost), - Comment(ApubComment), + PostOrComment(Box), PersonOrCommunity(Box), } #[derive(Deserialize)] #[serde(untagged)] pub(crate) enum SearchableKinds { - Page(Box), - Note(Note), + PageOrNote(Box), PersonOrGroup(Box), } @@ -75,8 +70,7 @@ impl Object for SearchableObjects { fn last_refreshed_at(&self) -> Option> { match self { - SearchableObjects::Post(p) => p.last_refreshed_at(), - SearchableObjects::Comment(c) => c.last_refreshed_at(), + SearchableObjects::PostOrComment(p) => p.last_refreshed_at(), SearchableObjects::PersonOrCommunity(p) => p.last_refreshed_at(), } } @@ -95,13 +89,9 @@ impl Object for SearchableObjects { if let Some(uc) = uc { return Ok(Some(SearchableObjects::PersonOrCommunity(Box::new(uc)))); } - let p = ApubPost::read_from_id(object_id.clone(), context).await?; - if let Some(p) = p { - return Ok(Some(SearchableObjects::Post(p))); - } - let c = ApubComment::read_from_id(object_id, context).await?; - if let Some(c) = c { - return Ok(Some(SearchableObjects::Comment(c))); + let pc = PostOrComment::read_from_id(object_id.clone(), context).await?; + if let Some(pc) = pc { + return Ok(Some(SearchableObjects::PostOrComment(Box::new(pc)))); } Ok(None) } @@ -109,25 +99,16 @@ impl Object for SearchableObjects { #[tracing::instrument(skip_all)] async fn delete(self, data: &Data) -> LemmyResult<()> { match self { - SearchableObjects::Post(p) => p.delete(data).await, - SearchableObjects::Comment(c) => c.delete(data).await, - SearchableObjects::PersonOrCommunity(pc) => match *pc { - UserOrCommunity::User(p) => p.delete(data).await, - UserOrCommunity::Community(c) => c.delete(data).await, - }, + SearchableObjects::PostOrComment(pc) => pc.delete(data).await, + SearchableObjects::PersonOrCommunity(pc) => pc.delete(data).await, } } async fn into_json(self, data: &Data) -> LemmyResult { + use SearchableObjects::*; Ok(match self { - SearchableObjects::Post(p) => SearchableKinds::Page(Box::new(p.into_json(data).await?)), - SearchableObjects::Comment(c) => SearchableKinds::Note(c.into_json(data).await?), - SearchableObjects::PersonOrCommunity(pc) => { - SearchableKinds::PersonOrGroup(Box::new(match *pc { - UserOrCommunity::User(p) => PersonOrGroup::Person(p.into_json(data).await?), - UserOrCommunity::Community(c) => PersonOrGroup::Group(c.into_json(data).await?), - })) - } + PostOrComment(pc) => SearchableKinds::PageOrNote(Box::new(pc.into_json(data).await?)), + PersonOrCommunity(pc) => SearchableKinds::PersonOrGroup(Box::new(pc.into_json(data).await?)), }) } @@ -137,24 +118,20 @@ impl Object for SearchableObjects { expected_domain: &Url, data: &Data, ) -> LemmyResult<()> { + use SearchableKinds::*; match apub { - SearchableKinds::Page(a) => ApubPost::verify(a, expected_domain, data).await, - SearchableKinds::Note(a) => ApubComment::verify(a, expected_domain, data).await, - SearchableKinds::PersonOrGroup(pg) => match pg.as_ref() { - PersonOrGroup::Person(a) => ApubPerson::verify(a, expected_domain, data).await, - PersonOrGroup::Group(a) => ApubCommunity::verify(a, expected_domain, data).await, - }, + PageOrNote(pn) => PostOrComment::verify(pn, expected_domain, data).await, + PersonOrGroup(pg) => UserOrCommunity::verify(pg, expected_domain, data).await, } } #[tracing::instrument(skip_all)] async fn from_json(apub: Self::Kind, context: &Data) -> LemmyResult { - use SearchableKinds as SAT; + use SearchableKinds::*; use SearchableObjects as SO; Ok(match apub { - SAT::Page(p) => SO::Post(ApubPost::from_json(*p, context).await?), - SAT::Note(n) => SO::Comment(ApubComment::from_json(n, context).await?), - SAT::PersonOrGroup(pg) => { + PageOrNote(pg) => SO::PostOrComment(Box::new(PostOrComment::from_json(*pg, context).await?)), + PersonOrGroup(pg) => { SO::PersonOrCommunity(Box::new(UserOrCommunity::from_json(*pg, context).await?)) } }) diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index 17711817e..d6b3c818d 100644 --- a/crates/apub/src/http/comment.rs +++ b/crates/apub/src/http/comment.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ source::{comment::Comment, community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::Deserialize; #[derive(Deserialize)] @@ -31,16 +31,9 @@ pub(crate) async fn get_apub_comment( ) -> LemmyResult { let id = CommentId(info.comment_id.parse::()?); // Can't use CommentView here because it excludes deleted/removed/local-only items - let comment: ApubComment = Comment::read(&mut context.pool(), id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)? - .into(); - let post = Post::read(&mut context.pool(), comment.post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let comment: ApubComment = Comment::read(&mut context.pool(), id).await?.into(); + let post = Post::read(&mut context.pool(), comment.post_id).await?; + let community = Community::read(&mut context.pool(), post.community_id).await?; check_community_public(&community)?; if !comment.local { diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 0f6ee57cb..37482aedb 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -1,5 +1,4 @@ use crate::{ - activity_lists::GroupInboxActivities, collections::{ community_featured::ApubCommunityFeatured, community_follower::ApubCommunityFollower, @@ -7,15 +6,13 @@ use crate::{ community_outbox::ApubCommunityOutbox, }, http::{check_community_public, create_apub_response, create_apub_tombstone_response}, - objects::{community::ApubCommunity, person::ApubPerson}, + objects::community::ApubCommunity, }; use activitypub_federation::{ - actix_web::inbox::receive_activity, config::Data, - protocol::context::WithContext, traits::{Collection, Object}, }; -use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; +use actix_web::{web, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::ApubActor}; use lemmy_utils::{error::LemmyResult, LemmyErrorType}; @@ -35,7 +32,7 @@ pub(crate) async fn get_apub_community_http( let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, true) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? + .ok_or(LemmyErrorType::NotFound)? .into(); if community.deleted || community.removed { @@ -47,19 +44,6 @@ pub(crate) async fn get_apub_community_http( create_apub_response(&apub) } -/// Handler for all incoming receive to community inboxes. -#[tracing::instrument(skip_all)] -pub async fn community_inbox( - request: HttpRequest, - body: Bytes, - data: Data, -) -> LemmyResult { - receive_activity::, ApubPerson, LemmyContext>( - request, body, &data, - ) - .await -} - /// Returns an empty followers collection, only populating the size (for privacy). pub(crate) async fn get_apub_community_followers( info: web::Path, @@ -67,7 +51,7 @@ pub(crate) async fn get_apub_community_followers( ) -> LemmyResult { let community = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .ok_or(LemmyErrorType::NotFound)?; check_community_public(&community)?; let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?; create_apub_response(&followers) @@ -82,7 +66,7 @@ pub(crate) async fn get_apub_community_outbox( let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? + .ok_or(LemmyErrorType::NotFound)? .into(); check_community_public(&community)?; let outbox = ApubCommunityOutbox::read_local(&community, &context).await?; @@ -97,7 +81,7 @@ pub(crate) async fn get_apub_community_moderators( let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? + .ok_or(LemmyErrorType::NotFound)? .into(); check_community_public(&community)?; let moderators = ApubCommunityModerators::read_local(&community, &context).await?; @@ -112,7 +96,7 @@ pub(crate) async fn get_apub_community_featured( let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)? + .ok_or(LemmyErrorType::NotFound)? .into(); check_community_public(&community)?; let featured = ApubCommunityFeatured::read_local(&community, &context).await?; @@ -120,8 +104,6 @@ pub(crate) async fn get_apub_community_featured( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] pub(crate) mod tests { use super::*; @@ -151,14 +133,16 @@ pub(crate) mod tests { Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?; create_local_site(context, instance.id).await?; - let community_form = CommunityInsertForm::builder() - .name("testcom6".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .deleted(Some(deleted)) - .visibility(Some(visibility)) - .build(); + let community_form = CommunityInsertForm { + deleted: Some(deleted), + visibility: Some(visibility), + ..CommunityInsertForm::new( + instance.id, + "testcom6".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ) + }; let community = Community::create(&mut context.pool(), &community_form).await?; Ok((instance, community)) } @@ -169,24 +153,19 @@ pub(crate) mod tests { instance_id: InstanceId, ) -> LemmyResult<()> { // Create a local site, since this is necessary for community fetching. - let site_form = SiteInsertForm::builder() - .name("test site".to_string()) - .instance_id(instance_id) - .build(); + let site_form = SiteInsertForm::new("test site".to_string(), instance_id); let site = Site::create(&mut context.pool(), &site_form).await?; - let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build(); + let local_site_form = LocalSiteInsertForm::new(site.id); let local_site = LocalSite::create(&mut context.pool(), &local_site_form).await?; - let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::builder() - .local_site_id(local_site.id) - .build(); + let local_site_rate_limit_form = LocalSiteRateLimitInsertForm::new(local_site.id); LocalSiteRateLimit::create(&mut context.pool(), &local_site_rate_limit_form).await?; Ok(()) } async fn decode_response(res: HttpResponse) -> LemmyResult { - let body = to_bytes(res.into_body()).await.unwrap(); + let body = to_bytes(res.into_body()).await.unwrap_or_default(); let body = std::str::from_utf8(&body)?; Ok(serde_json::from_str(body)?) } diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index d913a14f3..bc148eb9c 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -107,8 +107,8 @@ pub(crate) async fn get_activity( ))? .into(); let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id) - .await? - .ok_or(FederationError::CouldntFindActivity)?; + .await + .map_err(|_| FederationError::CouldntFindActivity)?; let sensitive = activity.sensitive; if sensitive { @@ -124,7 +124,7 @@ fn check_community_public(community: &Community) -> LemmyResult<()> { Err(LemmyErrorType::Deleted)? } if community.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::CouldntFindCommunity.into()); + return Err(LemmyErrorType::NotFound.into()); } Ok(()) } diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index ba2372fe8..0f628c497 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -1,17 +1,10 @@ use crate::{ - activity_lists::PersonInboxActivities, - fetcher::user_or_community::UserOrCommunity, http::{create_apub_response, create_apub_tombstone_response}, objects::person::ApubPerson, protocol::collections::empty_outbox::EmptyOutbox, }; -use activitypub_federation::{ - actix_web::inbox::receive_activity, - config::Data, - protocol::context::WithContext, - traits::Object, -}; -use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; +use activitypub_federation::{config::Data, traits::Object}; +use actix_web::{web, HttpResponse}; use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url}; use lemmy_db_schema::{source::person::Person, traits::ApubActor}; use lemmy_utils::{error::LemmyResult, LemmyErrorType}; @@ -32,7 +25,7 @@ pub(crate) async fn get_apub_person_http( // TODO: this needs to be able to read deleted persons, so that it can send tombstones let person: ApubPerson = Person::read_from_name(&mut context.pool(), &user_name, true) .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? + .ok_or(LemmyErrorType::NotFound)? .into(); if !person.deleted { @@ -44,18 +37,6 @@ pub(crate) async fn get_apub_person_http( } } -#[tracing::instrument(skip_all)] -pub async fn person_inbox( - request: HttpRequest, - body: Bytes, - data: Data, -) -> LemmyResult { - receive_activity::, UserOrCommunity, LemmyContext>( - request, body, &data, - ) - .await -} - #[tracing::instrument(skip_all)] pub(crate) async fn get_apub_person_outbox( info: web::Path, @@ -63,7 +44,7 @@ pub(crate) async fn get_apub_person_outbox( ) -> LemmyResult { let person = Person::read_from_name(&mut context.pool(), &info.user_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + .ok_or(LemmyErrorType::NotFound)?; let outbox_id = generate_outbox_url(&person.actor_id)?.into(); let outbox = EmptyOutbox::new(outbox_id)?; create_apub_response(&outbox) diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs index 513cba7ea..ce6612826 100644 --- a/crates/apub/src/http/post.rs +++ b/crates/apub/src/http/post.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::Deserialize; #[derive(Deserialize)] @@ -31,13 +31,8 @@ pub(crate) async fn get_apub_post( ) -> LemmyResult { let id = PostId(info.post_id.parse::()?); // Can't use PostView here because it excludes deleted/removed/local-only items - let post: ApubPost = Post::read(&mut context.pool(), id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)? - .into(); - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let post: ApubPost = Post::read(&mut context.pool(), id).await?.into(); + let community = Community::read(&mut context.pool(), post.community_id).await?; check_community_public(&community)?; if !post.local { diff --git a/crates/apub/src/http/routes.rs b/crates/apub/src/http/routes.rs index ab046afe1..9479e6312 100644 --- a/crates/apub/src/http/routes.rs +++ b/crates/apub/src/http/routes.rs @@ -1,7 +1,6 @@ use crate::http::{ comment::get_apub_comment, community::{ - community_inbox, get_apub_community_featured, get_apub_community_followers, get_apub_community_http, @@ -9,7 +8,7 @@ use crate::http::{ get_apub_community_outbox, }, get_activity, - person::{get_apub_person_http, get_apub_person_outbox, person_inbox}, + person::{get_apub_person_http, get_apub_person_outbox}, post::get_apub_post, shared_inbox, site::{get_apub_site_http, get_apub_site_outbox}, @@ -56,8 +55,6 @@ pub fn config(cfg: &mut web::ServiceConfig) { cfg.service( web::scope("") .guard(InboxRequestGuard) - .route("/c/{community_name}/inbox", web::post().to(community_inbox)) - .route("/u/{user_name}/inbox", web::post().to(person_inbox)) .route("/inbox", web::post().to(shared_inbox)), ); } diff --git a/crates/apub/src/http/site.rs b/crates/apub/src/http/site.rs index 54d3c0e32..95175a006 100644 --- a/crates/apub/src/http/site.rs +++ b/crates/apub/src/http/site.rs @@ -6,16 +6,12 @@ use crate::{ use activitypub_federation::{config::Data, traits::Object}; use actix_web::HttpResponse; use lemmy_api_common::context::LemmyContext; -use lemmy_db_views::structs::SiteView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_db_schema::source::site::Site; +use lemmy_utils::error::LemmyResult; use url::Url; pub(crate) async fn get_apub_site_http(context: Data) -> LemmyResult { - let site: ApubSite = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)? - .site - .into(); + let site: ApubSite = Site::read_local(&mut context.pool()).await?.into(); let apub = site.into_json(&context).await?; create_apub_response(&apub) diff --git a/crates/apub/src/mentions.rs b/crates/apub/src/mentions.rs index 4c56f1715..cb46be52a 100644 --- a/crates/apub/src/mentions.rs +++ b/crates/apub/src/mentions.rs @@ -14,7 +14,6 @@ use lemmy_db_schema::{ use lemmy_utils::{ error::{FederationError, LemmyResult}, utils::mention::scrape_text_for_mentions, - LemmyErrorType, }; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -103,21 +102,12 @@ async fn get_comment_parent_creator( comment: &Comment, ) -> LemmyResult { let parent_creator_id = if let Some(parent_comment_id) = comment.parent_comment_id() { - let parent_comment = Comment::read(pool, parent_comment_id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let parent_comment = Comment::read(pool, parent_comment_id).await?; parent_comment.creator_id } else { let parent_post_id = comment.post_id; - let parent_post = Post::read(pool, parent_post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let parent_post = Post::read(pool, parent_post_id).await?; parent_post.creator_id }; - Ok( - Person::read(pool, parent_creator_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? - .into(), - ) + Ok(Person::read(pool, parent_creator_id).await?.into()) } diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 18f1c6c61..403ecbf94 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -1,6 +1,7 @@ use crate::{ activities::{verify_is_public, verify_person_in_community}, check_apub_id_valid_with_strictness, + fetcher::markdown_links::markdown_rewrite_remote_links, mentions::collect_non_local_mentions, objects::{read_from_string_or_source, verify_is_remote_object}, protocol::{ @@ -32,7 +33,7 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, + error::{FederationError, LemmyError, LemmyResult}, utils::markdown::markdown_to_html, }; use std::ops::Deref; @@ -91,28 +92,20 @@ impl Object for ApubComment { #[tracing::instrument(skip_all)] async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let creator = Person::read(&mut context.pool(), creator_id).await?; let post_id = self.post_id; - let post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), post_id).await?; let community_id = post.community_id; - let community = Community::read(&mut context.pool(), community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), community_id).await?; let in_reply_to = if let Some(comment_id) = self.parent_comment_id() { - let parent_comment = Comment::read(&mut context.pool(), comment_id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let parent_comment = Comment::read(&mut context.pool(), comment_id).await?; parent_comment.ap_id.into() } else { post.ap_id.into() }; - let language = LanguageTag::new_single(self.language_id, &mut context.pool()).await?; + let language = Some(LanguageTag::new_single(self.language_id, &mut context.pool()).await?); let maa = collect_non_local_mentions(&self, community.actor_id.clone().into(), context).await?; let note = Note { @@ -136,6 +129,8 @@ impl Object for ApubComment { Ok(note) } + /// Recursively fetches all parent comments. This can lead to a stack overflow so we need to + /// Box::pin all large futures on the heap. #[tracing::instrument(skip_all)] async fn verify( note: &Note, @@ -145,14 +140,24 @@ impl Object for ApubComment { verify_domains_match(note.id.inner(), expected_domain)?; verify_domains_match(note.attributed_to.inner(), note.id.inner())?; verify_is_public(¬e.to, ¬e.cc)?; - let community = note.community(context).await?; + let community = Box::pin(note.community(context)).await?; - check_apub_id_valid_with_strictness(note.id.inner(), community.local, context).await?; + Box::pin(check_apub_id_valid_with_strictness( + note.id.inner(), + community.local, + context, + )) + .await?; verify_is_remote_object(¬e.id, context)?; - verify_person_in_community(¬e.attributed_to, &community, context).await?; + Box::pin(verify_person_in_community( + ¬e.attributed_to, + &community, + context, + )) + .await?; - let (post, _) = note.get_parents(context).await?; - let creator = note.attributed_to.dereference(context).await?; + let (post, _) = Box::pin(note.get_parents(context)).await?; + let creator = Box::pin(note.attributed_to.dereference(context)).await?; let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &creator, community.id) .await .is_ok(); @@ -177,8 +182,11 @@ impl Object for ApubComment { let slur_regex = &local_site_opt_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(context).await?; let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?; - let language_id = - LanguageTag::to_language_id_single(note.language, &mut context.pool()).await?; + let content = markdown_rewrite_remote_links(content, context).await; + let language_id = Some( + LanguageTag::to_language_id_single(note.language.unwrap_or_default(), &mut context.pool()) + .await?, + ); let form = CommentInsertForm { creator_id: creator.id, @@ -292,7 +300,7 @@ pub(crate) mod tests { let comment = ApubComment::from_json(json, &context).await?; assert_eq!(comment.ap_id, pleroma_url.into()); - assert_eq!(comment.content.len(), 64); + assert_eq!(comment.content.len(), 10); assert!(!comment.local); assert_eq!(context.request_count(), 1); diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 93c2c83ae..c8479eaba 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -1,10 +1,11 @@ use crate::{ activities::GetActorType, check_apub_id_valid, + fetcher::markdown_links::markdown_rewrite_remote_links_opt, local_site_data_cached, objects::{instance::fetch_instance_actor_for_object, read_from_string_or_source_opt}, protocol::{ - objects::{group::Group, Endpoints, LanguageTag}, + objects::{group::Group, LanguageTag}, ImageObject, Source, }, @@ -115,9 +116,7 @@ impl Object for ApubCommunity { inbox: self.inbox_url.clone().into(), outbox: generate_outbox_url(&self.actor_id)?.into(), followers: self.followers_url.clone().map(Into::into), - endpoints: self.shared_inbox_url.clone().map(|s| Endpoints { - shared_inbox: s.into(), - }), + endpoints: None, public_key: self.public_key(), language, published: Some(self.published), @@ -148,31 +147,38 @@ impl Object for ApubCommunity { let description = read_from_string_or_source_opt(&group.summary, &None, &group.source); let description = process_markdown_opt(&description, slur_regex, &url_blocklist, context).await?; + let description = markdown_rewrite_remote_links_opt(description, context).await; let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?; let form = CommunityInsertForm { - name: group.preferred_username.clone(), - title: group.name.unwrap_or(group.preferred_username.clone()), - description, published: group.published, updated: group.updated, deleted: Some(false), nsfw: Some(group.sensitive.unwrap_or(false)), actor_id: Some(group.id.into()), local: Some(false), - public_key: group.public_key.public_key_pem, last_refreshed_at: Some(naive_now()), icon, banner, + description, followers_url: group.followers.clone().map(Into::into), - inbox_url: Some(group.inbox.into()), - shared_inbox_url: group.endpoints.map(|e| e.shared_inbox.into()), + inbox_url: Some( + group + .endpoints + .map(|e| e.shared_inbox) + .unwrap_or(group.inbox) + .into(), + ), moderators_url: group.attributed_to.clone().map(Into::into), posting_restricted_to_mods: group.posting_restricted_to_mods, - instance_id, featured_url: group.featured.clone().map(Into::into), - ..Default::default() + ..CommunityInsertForm::new( + instance_id, + group.preferred_username.clone(), + group.name.unwrap_or(group.preferred_username.clone()), + group.public_key.public_key_pem, + ) }; let languages = LanguageTag::to_language_id_multiple(group.language, &mut context.pool()).await?; @@ -222,7 +228,7 @@ impl Actor for ApubCommunity { } fn shared_inbox(&self) -> Option { - self.shared_inbox_url.clone().map(Into::into) + None } } @@ -295,7 +301,7 @@ pub(crate) mod tests { assert!(!community.local); assert_eq!( community.description.as_ref().map(std::string::String::len), - Some(132) + Some(63) ); Community::delete(&mut context.pool(), community.id).await?; diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 6596320bc..a123c85ba 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -2,6 +2,7 @@ use super::verify_is_remote_object; use crate::{ activities::GetActorType, check_apub_id_valid_with_strictness, + fetcher::markdown_links::markdown_rewrite_remote_links_opt, local_site_data_cached, objects::read_from_string_or_source_opt, protocol::{ @@ -150,6 +151,7 @@ impl Object for ApubSite { let url_blocklist = get_url_blocklist(context).await?; let sidebar = read_from_string_or_source_opt(&apub.content, &None, &apub.source); let sidebar = process_markdown_opt(&sidebar, slur_regex, &url_blocklist, context).await?; + let sidebar = markdown_rewrite_remote_links_opt(sidebar, context).await; let icon = proxy_image_link_opt_apub(apub.icon.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(apub.image.map(|i| i.url), context).await?; diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 61ff04622..4e8519f78 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -2,13 +2,11 @@ use super::verify_is_remote_object; use crate::{ activities::GetActorType, check_apub_id_valid_with_strictness, + fetcher::markdown_links::markdown_rewrite_remote_links_opt, local_site_data_cached, objects::{instance::fetch_instance_actor_for_object, read_from_string_or_source_opt}, protocol::{ - objects::{ - person::{Person, UserTypes}, - Endpoints, - }, + objects::person::{Person, UserTypes}, ImageObject, Source, }, @@ -117,9 +115,7 @@ impl Object for ApubPerson { matrix_user_id: self.matrix_user_id.clone(), published: Some(self.published), outbox: generate_outbox_url(&self.actor_id)?.into(), - endpoints: self.shared_inbox_url.clone().map(|s| Endpoints { - shared_inbox: s.into(), - }), + endpoints: None, public_key: self.public_key(), updated: self.updated, inbox: self.inbox_url.clone().into(), @@ -156,6 +152,7 @@ impl Object for ApubPerson { let url_blocklist = get_url_blocklist(context).await?; let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source); let bio = process_markdown_opt(&bio, slur_regex, &url_blocklist, context).await?; + let bio = markdown_rewrite_remote_links_opt(bio, context).await; let avatar = proxy_image_link_opt_apub(person.icon.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(person.image.map(|i| i.url), context).await?; @@ -180,8 +177,13 @@ impl Object for ApubPerson { private_key: None, public_key: person.public_key.public_key_pem, last_refreshed_at: Some(naive_now()), - inbox_url: Some(person.inbox.into()), - shared_inbox_url: person.endpoints.map(|e| e.shared_inbox.into()), + inbox_url: Some( + person + .endpoints + .map(|e| e.shared_inbox) + .unwrap_or(person.inbox) + .into(), + ), matrix_user_id: person.matrix_user_id, instance_id, }; @@ -209,7 +211,7 @@ impl Actor for ApubPerson { } fn shared_inbox(&self) -> Option { - self.shared_inbox_url.clone().map(Into::into) + None } } @@ -277,7 +279,7 @@ pub(crate) mod tests { assert_eq!(person.name, "lanodan"); assert!(!person.local); assert_eq!(context.request_count(), 0); - assert_eq!(person.bio.as_ref().map(std::string::String::len), Some(873)); + assert_eq!(person.bio.as_ref().map(std::string::String::len), Some(812)); cleanup((person, site), &context).await?; Ok(()) diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 44e842413..ee88cf3ec 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -1,6 +1,7 @@ use crate::{ activities::{verify_is_public, verify_person_in_community}, check_apub_id_valid_with_strictness, + fetcher::markdown_links::{markdown_rewrite_remote_links_opt, to_local_url}, local_site_data_cached, objects::{read_from_string_or_source_opt, verify_is_remote_object}, protocol::{ @@ -39,7 +40,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyResult}, spawn_try_task, utils::{ markdown::markdown_to_html, @@ -107,14 +108,10 @@ impl Object for ApubPost { #[tracing::instrument(skip_all)] async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let creator = Person::read(&mut context.pool(), creator_id).await?; let community_id = self.community_id; - let community = Community::read(&mut context.pool(), community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; - let language = LanguageTag::new_single(self.language_id, &mut context.pool()).await?; + let community = Community::read(&mut context.pool(), community_id).await?; + let language = Some(LanguageTag::new_single(self.language_id, &mut context.pool()).await?); let attachment = self .url @@ -184,15 +181,12 @@ impl Object for ApubPost { let creator = page.creator()?.dereference(context).await?; let community = page.community(context).await?; if community.posting_restricted_to_mods { - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community.id, creator.id, ) .await?; - if !is_mod { - Err(LemmyErrorType::OnlyModsCanPostInCommunity)? - } } let mut name = page .name @@ -233,10 +227,13 @@ impl Object for ApubPost { let url_blocklist = get_url_blocklist(context).await?; - if let Some(url) = &url { - is_url_blocked(url, &url_blocklist)?; - is_valid_url(url)?; - } + let url = if let Some(url) = url { + is_url_blocked(&url, &url_blocklist)?; + is_valid_url(&url)?; + to_local_url(url.as_str(), context).await.or(Some(url)) + } else { + None + }; let alt_text = first_attachment.cloned().and_then(Attachment::alt_text); @@ -244,24 +241,25 @@ impl Object for ApubPost { let body = read_from_string_or_source_opt(&page.content, &page.media_type, &page.source); let body = process_markdown_opt(&body, slur_regex, &url_blocklist, context).await?; - let language_id = - LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?; + let body = markdown_rewrite_remote_links_opt(body, context).await; + let language_id = Some( + LanguageTag::to_language_id_single(page.language.unwrap_or_default(), &mut context.pool()) + .await?, + ); - let form = PostInsertForm::builder() - .name(name) - .url(url.map(Into::into)) - .body(body) - .alt_text(alt_text) - .creator_id(creator.id) - .community_id(community.id) - .published(page.published.map(Into::into)) - .updated(page.updated.map(Into::into)) - .deleted(Some(false)) - .nsfw(page.sensitive) - .ap_id(Some(page.id.clone().into())) - .local(Some(false)) - .language_id(language_id) - .build(); + let form = PostInsertForm { + url: url.map(Into::into), + body, + alt_text, + published: page.published.map(Into::into), + updated: page.updated.map(Into::into), + deleted: Some(false), + nsfw: page.sensitive, + ap_id: Some(page.id.clone().into()), + local: Some(false), + language_id, + ..PostInsertForm::new(name, creator.id, community.id) + }; let timestamp = page.updated.or(page.published).unwrap_or_else(naive_now); let post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?; @@ -270,9 +268,9 @@ impl Object for ApubPost { // 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 - }); + spawn_try_task( + async move { generate_post_link_metadata(post_, None, |_| None, context_).await }, + ); Ok(post.into()) } @@ -310,7 +308,7 @@ mod tests { assert_eq!(post.body.as_ref().map(std::string::String::len), Some(45)); assert!(!post.locked); assert!(!post.featured_community); - assert_eq!(context.request_count(), 0); + assert_eq!(context.request_count(), 1); Post::delete(&mut context.pool(), post.id).await?; Person::delete(&mut context.pool(), person.id).await?; diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 43246b77d..5a191cc66 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -1,6 +1,7 @@ use super::verify_is_remote_object; use crate::{ check_apub_id_valid_with_strictness, + fetcher::markdown_links::markdown_rewrite_remote_links, objects::read_from_string_or_source, protocol::{ objects::chat_message::{ChatMessage, ChatMessageType}, @@ -15,12 +16,13 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{check_person_block, get_url_blocklist, local_site_opt_to_slur_regex, process_markdown}, + utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown}, }; use lemmy_db_schema::{ source::{ local_site::LocalSite, person::Person, + person_block::PersonBlock, private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, @@ -73,20 +75,16 @@ impl Object for ApubPrivateMessage { async fn delete(self, _context: &Data) -> LemmyResult<()> { // do nothing, because pm can't be fetched over http - Err(LemmyErrorType::CouldntFindPrivateMessage.into()) + Err(LemmyErrorType::NotFound.into()) } #[tracing::instrument(skip_all)] async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let creator = Person::read(&mut context.pool(), creator_id).await?; let recipient_id = self.recipient_id; - let recipient = Person::read(&mut context.pool(), recipient_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let recipient = Person::read(&mut context.pool(), recipient_id).await?; let note = ChatMessage { r#type: ChatMessageType::ChatMessage, @@ -130,13 +128,14 @@ impl Object for ApubPrivateMessage { ) -> LemmyResult { let creator = note.attributed_to.dereference(context).await?; let recipient = note.to[0].dereference(context).await?; - check_person_block(creator.id, recipient.id, &mut context.pool()).await?; + PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?; let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(context).await?; let content = read_from_string_or_source(¬e.content, &None, ¬e.source); let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?; + let content = markdown_rewrite_remote_links(content, context).await; let form = PrivateMessageInsertForm { creator_id: creator.id, diff --git a/crates/apub/src/protocol/activities/block/undo_block_user.rs b/crates/apub/src/protocol/activities/block/undo_block_user.rs index 491ec7ed1..e038fa2dc 100644 --- a/crates/apub/src/protocol/activities/block/undo_block_user.rs +++ b/crates/apub/src/protocol/activities/block/undo_block_user.rs @@ -29,6 +29,10 @@ pub struct UndoBlockUser { pub(crate) kind: UndoType, pub(crate) id: Url, pub(crate) audience: Option>, + + /// Quick and dirty solution. + /// TODO: send a separate Delete activity instead + pub(crate) restore_data: Option, } #[async_trait::async_trait] diff --git a/crates/apub/src/protocol/activities/community/collection_add.rs b/crates/apub/src/protocol/activities/community/collection_add.rs index 0e2ab75a6..777ad8b62 100644 --- a/crates/apub/src/protocol/activities/community/collection_add.rs +++ b/crates/apub/src/protocol/activities/community/collection_add.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ }; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::community::Community; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -35,9 +35,7 @@ pub struct CollectionAdd { impl InCommunity for CollectionAdd { async fn community(&self, context: &Data) -> LemmyResult { let (community, _) = - Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/community/collection_remove.rs b/crates/apub/src/protocol/activities/community/collection_remove.rs index 51c4761ba..afc0c24a0 100644 --- a/crates/apub/src/protocol/activities/community/collection_remove.rs +++ b/crates/apub/src/protocol/activities/community/collection_remove.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ }; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::community::Community; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -35,9 +35,7 @@ pub struct CollectionRemove { impl InCommunity for CollectionRemove { async fn community(&self, context: &Data) -> LemmyResult { let (community, _) = - Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/community/lock_page.rs b/crates/apub/src/protocol/activities/community/lock_page.rs index a08b3c5a2..5c8ecfca9 100644 --- a/crates/apub/src/protocol/activities/community/lock_page.rs +++ b/crates/apub/src/protocol/activities/community/lock_page.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ }; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::Crud}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use strum::Display; use url::Url; @@ -55,9 +55,7 @@ pub struct UndoLockPage { impl InCommunity for LockPage { async fn community(&self, context: &Data) -> LemmyResult { let post = self.object.dereference(context).await?; - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/community/report.rs b/crates/apub/src/protocol/activities/community/report.rs index 7698cde50..dd0f72f43 100644 --- a/crates/apub/src/protocol/activities/community/report.rs +++ b/crates/apub/src/protocol/activities/community/report.rs @@ -38,7 +38,7 @@ impl Report { .summary .clone() .or(self.content.clone()) - .ok_or(LemmyErrorType::CouldntFindObject.into()) + .ok_or(LemmyErrorType::NotFound.into()) } } @@ -63,7 +63,7 @@ impl ReportObject { return deref; } } - Err(LemmyErrorType::CouldntFindObject.into()) + Err(LemmyErrorType::NotFound.into()) } } } diff --git a/crates/apub/src/protocol/activities/create_or_update/note.rs b/crates/apub/src/protocol/activities/create_or_update/note.rs index 43ffeb291..ff0728174 100644 --- a/crates/apub/src/protocol/activities/create_or_update/note.rs +++ b/crates/apub/src/protocol/activities/create_or_update/note.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ }; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::Crud}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -36,9 +36,7 @@ pub struct CreateOrUpdateNote { impl InCommunity for CreateOrUpdateNote { async fn community(&self, context: &Data) -> LemmyResult { let post = self.object.get_parents(context).await?.0; - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/deletion/delete.rs b/crates/apub/src/protocol/activities/deletion/delete.rs index 3b9aad079..3a29da069 100644 --- a/crates/apub/src/protocol/activities/deletion/delete.rs +++ b/crates/apub/src/protocol/activities/deletion/delete.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -51,9 +51,7 @@ impl InCommunity for Delete { let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? { DeletableObjects::Community(c) => c.id, DeletableObjects::Comment(c) => { - let post = Post::read(&mut context.pool(), c.post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), c.post_id).await?; post.community_id } DeletableObjects::Post(p) => p.community_id, @@ -62,9 +60,7 @@ impl InCommunity for Delete { return Err(anyhow!("Private message is not part of community").into()) } }; - let community = Community::read(&mut context.pool(), community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), community_id).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index a9eb74e0c..dbba1bb8a 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -30,21 +30,30 @@ pub(crate) struct LanguageTag { pub(crate) name: String, } +impl Default for LanguageTag { + fn default() -> Self { + LanguageTag { + identifier: "und".to_string(), + name: "Undetermined".to_string(), + } + } +} + impl LanguageTag { pub(crate) async fn new_single( lang: LanguageId, pool: &mut DbPool<'_>, - ) -> LemmyResult> { + ) -> LemmyResult { let lang = Language::read_from_id(pool, lang).await?; // undetermined if lang.id == UNDETERMINED_ID { - Ok(None) + Ok(LanguageTag::default()) } else { - Ok(Some(LanguageTag { + Ok(LanguageTag { identifier: lang.code, name: lang.name, - })) + }) } } @@ -69,13 +78,10 @@ impl LanguageTag { } pub(crate) async fn to_language_id_single( - lang: Option, + lang: Self, pool: &mut DbPool<'_>, - ) -> LemmyResult> { - let identifier = lang.map(|l| l.identifier); - let language = Language::read_id_from_code(pool, identifier.as_deref()).await?; - - Ok(language) + ) -> LemmyResult { + Ok(Language::read_id_from_code(pool, &lang.identifier).await?) } pub(crate) async fn to_language_id_multiple( @@ -86,10 +92,10 @@ impl LanguageTag { for l in langs { let id = l.identifier; - language_ids.push(Language::read_id_from_code(pool, Some(&id)).await?); + language_ids.push(Language::read_id_from_code(pool, &id).await?); } - Ok(language_ids.into_iter().flatten().collect()) + Ok(language_ids.into_iter().collect()) } } diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index b0ae00037..e3e204254 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -20,10 +20,9 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::{error::LemmyResult, LemmyErrorType, MAX_COMMENT_DEPTH_LIMIT}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use std::ops::Deref; use url::Url; #[skip_serializing_none] @@ -58,15 +57,23 @@ impl Note { &self, context: &Data, ) -> LemmyResult<(ApubPost, Option)> { - // Fetch parent comment chain in a box, otherwise it can cause a stack overflow. - let parent = Box::pin(self.in_reply_to.dereference(context).await?); - match parent.deref() { + // We use recursion here to fetch the entire comment chain up to the top-level parent. This is + // necessary because we need to know the post and parent comment in order to insert a new + // comment. However it can also lead to stack overflow when fetching many comments recursively. + // To avoid this we check the request count against max comment depth, which based on testing + // can be handled without risking stack overflow. This is not a perfect solution, because in + // some cases we have to fetch user profiles too, and reach the limit after only 25 comments + // or so. + // A cleaner solution would be converting the recursion into a loop, but that is tricky. + if context.request_count() > MAX_COMMENT_DEPTH_LIMIT as u32 { + Err(LemmyErrorType::MaxCommentDepthReached)?; + } + let parent = self.in_reply_to.dereference(context).await?; + match parent { PostOrComment::Post(p) => Ok((p.clone(), None)), PostOrComment::Comment(c) => { let post_id = c.post_id; - let post = Post::read(&mut context.pool(), post_id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let post = Post::read(&mut context.pool(), post_id).await?; Ok((post.into(), Some(c.clone()))) } } @@ -77,9 +84,7 @@ impl Note { impl InCommunity for Note { async fn community(&self, context: &Data) -> LemmyResult { let (post, _) = self.get_parents(context).await?; - let community = Community::read(&mut context.pool(), post.community_id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let community = Community::read(&mut context.pool(), post.community_id).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 9d10f9a9e..97f767573 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -226,7 +226,7 @@ impl InCommunity for Page { break c; } } else { - Err(LemmyErrorType::CouldntFindCommunity)?; + Err(LemmyErrorType::NotFound)?; } } } @@ -234,7 +234,7 @@ impl InCommunity for Page { p.iter() .find(|a| a.kind == PersonOrGroupType::Group) .map(|a| ObjectId::::from(a.id.clone().into_inner())) - .ok_or(LemmyErrorType::CouldntFindCommunity)? + .ok_or(LemmyErrorType::NotFound)? .dereference(context) .await? } diff --git a/crates/db_perf/src/main.rs b/crates/db_perf/src/main.rs index 8e03a0a1d..0fa5c0549 100644 --- a/crates/db_perf/src/main.rs +++ b/crates/db_perf/src/main.rs @@ -20,7 +20,7 @@ use lemmy_db_schema::{ }, traits::Crud, utils::{build_db_pool, get_conn, now}, - SortType, + PostSortType, }; use lemmy_db_views::{post_view::PostQuery, structs::PaginationCursor}; use lemmy_utils::error::{LemmyErrorExt2, LemmyResult}; @@ -79,11 +79,12 @@ async fn try_main() -> LemmyResult<()> { println!("🌍 creating {} communities", args.communities); let mut community_ids = vec![]; for i in 0..args.communities.get() { - let form = CommunityInsertForm::builder() - .name(format!("c{i}")) - .title(i.to_string()) - .instance_id(instance.id) - .build(); + let form = CommunityInsertForm::new( + instance.id, + format!("c{i}"), + i.to_string(), + "pubkey".to_string(), + ); community_ids.push(Community::create(&mut conn.into(), &form).await?.id); } @@ -151,7 +152,7 @@ async fn try_main() -> LemmyResult<()> { // TODO: include local_user let post_views = PostQuery { community_id: community_ids.as_slice().first().cloned(), - sort: Some(SortType::New), + sort: Some(PostSortType::New), limit: Some(20), page_after, ..Default::default() diff --git a/crates/db_perf/src/series.rs b/crates/db_perf/src/series.rs index b504efc54..8efc078b1 100644 --- a/crates/db_perf/src/series.rs +++ b/crates/db_perf/src/series.rs @@ -75,7 +75,7 @@ impl> ValidGrouping<()> type IsAggregate = is_aggregate::No; } -#[allow(non_camel_case_types)] +#[expect(non_camel_case_types)] #[derive(QueryId, Clone, Copy, Debug)] pub struct current_value; diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 57af68f46..aeaa55c93 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -64,7 +64,6 @@ diesel-async = { workspace = true, features = [ ], optional = true } regex = { workspace = true, optional = true } diesel_ltree = { workspace = true, optional = true } -typed-builder = { workspace = true } async-trait = { workspace = true } tracing = { workspace = true } deadpool = { version = "0.12.1", features = ["rt_tokio_1"], optional = true } diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 92b24beb5..a97bb565b 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -1,6 +1,5 @@ use crate::{ aggregates::structs::CommentAggregates, - diesel::OptionalExtension, newtypes::CommentId, schema::comment_aggregates, utils::{functions::hot_rank, get_conn, DbPool}, @@ -9,13 +8,9 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl CommentAggregates { - pub async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result { let conn = &mut get_conn(pool).await?; - comment_aggregates::table - .find(comment_id) - .first(conn) - .await - .optional() + comment_aggregates::table.find(comment_id).first(conn).await } pub async fn update_hot_rank( @@ -35,8 +30,6 @@ impl CommentAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -51,76 +44,65 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_comment_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_comment_agg"); - let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); + let another_inserted_person = Person::create(pool, &another_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL_comment_agg".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_comment_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).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.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 child_comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); + let comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; + let child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let _inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; let comment_like = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; - CommentLike::like(pool, &comment_like).await.unwrap(); + CommentLike::like(pool, &comment_like).await?; - let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id) - .await - .unwrap() - .unwrap(); + let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id).await?; assert_eq!(1, comment_aggs_before_delete.score); assert_eq!(1, comment_aggs_before_delete.upvotes); @@ -129,56 +111,43 @@ mod tests { // Add a post dislike from the other person let comment_dislike = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: another_inserted_person.id, score: -1, }; - CommentLike::like(pool, &comment_dislike).await.unwrap(); + CommentLike::like(pool, &comment_dislike).await?; - let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id) - .await - .unwrap() - .unwrap(); + let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id).await?; assert_eq!(0, comment_aggs_after_dislike.score); assert_eq!(1, comment_aggs_after_dislike.upvotes); assert_eq!(1, comment_aggs_after_dislike.downvotes); // Remove the first comment like - CommentLike::remove(pool, inserted_person.id, inserted_comment.id) - .await - .unwrap(); - let after_like_remove = CommentAggregates::read(pool, inserted_comment.id) - .await - .unwrap() - .unwrap(); + CommentLike::remove(pool, inserted_person.id, inserted_comment.id).await?; + let after_like_remove = CommentAggregates::read(pool, inserted_comment.id).await?; assert_eq!(-1, after_like_remove.score); assert_eq!(0, after_like_remove.upvotes); assert_eq!(1, after_like_remove.downvotes); // Remove the parent post - Post::delete(pool, inserted_post.id).await.unwrap(); + Post::delete(pool, inserted_post.id).await?; // Should be none found, since the post was deleted - let after_delete = CommentAggregates::read(pool, inserted_comment.id) - .await - .unwrap(); - assert!(after_delete.is_none()); + let after_delete = CommentAggregates::read(pool, inserted_comment.id).await; + assert!(after_delete.is_err()); // This should delete all the associated rows, and fire triggers - Person::delete(pool, another_inserted_person.id) - .await - .unwrap(); - let person_num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + Person::delete(pool, another_inserted_person.id).await?; + let person_num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, person_num_deleted); // Delete the community - let community_num_deleted = Community::delete(pool, inserted_community.id) - .await - .unwrap(); + let community_num_deleted = Community::delete(pool, inserted_community.id).await?; assert_eq!(1, community_num_deleted); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index fe9de62bb..0359d8632 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -1,6 +1,5 @@ use crate::{ aggregates::structs::CommunityAggregates, - diesel::OptionalExtension, newtypes::CommunityId, schema::{community_aggregates, community_aggregates::subscribers}, utils::{get_conn, DbPool}, @@ -9,16 +8,12 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl CommunityAggregates { - pub async fn read( - pool: &mut DbPool<'_>, - for_community_id: CommunityId, - ) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, for_community_id: CommunityId) -> Result { let conn = &mut get_conn(pool).await?; community_aggregates::table .find(for_community_id) .first(conn) .await - .optional() } pub async fn update_federated_followers( @@ -36,8 +31,6 @@ impl CommunityAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -52,44 +45,41 @@ mod tests { traits::{Crud, Followable}, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg"); - let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); + let another_inserted_person = Person::create(pool, &another_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL_community_agg".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_community_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); - - let another_community = CommunityInsertForm::builder() - .name("TIL_community_agg_2".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - - let another_inserted_community = Community::create(pool, &another_community).await.unwrap(); + let another_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_community_agg_2".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let another_inserted_community = Community::create(pool, &another_community).await?; let first_person_follow = CommunityFollowerForm { community_id: inserted_community.id, @@ -97,9 +87,7 @@ mod tests { pending: false, }; - CommunityFollower::follow(pool, &first_person_follow) - .await - .unwrap(); + CommunityFollower::follow(pool, &first_person_follow).await?; let second_person_follow = CommunityFollowerForm { community_id: inserted_community.id, @@ -107,9 +95,7 @@ mod tests { pending: false, }; - CommunityFollower::follow(pool, &second_person_follow) - .await - .unwrap(); + CommunityFollower::follow(pool, &second_person_follow).await?; let another_community_follow = CommunityFollowerForm { community_id: another_inserted_community.id, @@ -117,41 +103,32 @@ mod tests { pending: false, }; - CommunityFollower::follow(pool, &another_community_follow) - .await - .unwrap(); + CommunityFollower::follow(pool, &another_community_follow).await?; - let new_post = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; - 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 child_comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); + let comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; + let child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let _inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; - let community_aggregates_before_delete = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap() - .unwrap(); + let community_aggregates_before_delete = + CommunityAggregates::read(pool, inserted_community.id).await?; assert_eq!(2, community_aggregates_before_delete.subscribers); assert_eq!(2, community_aggregates_before_delete.subscribers_local); @@ -159,76 +136,53 @@ mod tests { assert_eq!(2, community_aggregates_before_delete.comments); // Test the other community - let another_community_aggs = CommunityAggregates::read(pool, another_inserted_community.id) - .await - .unwrap() - .unwrap(); + let another_community_aggs = + CommunityAggregates::read(pool, another_inserted_community.id).await?; assert_eq!(1, another_community_aggs.subscribers); assert_eq!(1, another_community_aggs.subscribers_local); assert_eq!(0, another_community_aggs.posts); assert_eq!(0, another_community_aggs.comments); // Unfollow test - CommunityFollower::unfollow(pool, &second_person_follow) - .await - .unwrap(); - let after_unfollow = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap() - .unwrap(); + CommunityFollower::unfollow(pool, &second_person_follow).await?; + let after_unfollow = CommunityAggregates::read(pool, inserted_community.id).await?; assert_eq!(1, after_unfollow.subscribers); assert_eq!(1, after_unfollow.subscribers_local); // Follow again just for the later tests - CommunityFollower::follow(pool, &second_person_follow) - .await - .unwrap(); - let after_follow_again = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap() - .unwrap(); + CommunityFollower::follow(pool, &second_person_follow).await?; + let after_follow_again = CommunityAggregates::read(pool, inserted_community.id).await?; assert_eq!(2, after_follow_again.subscribers); assert_eq!(2, after_follow_again.subscribers_local); // Remove a parent post (the comment count should also be 0) - Post::delete(pool, inserted_post.id).await.unwrap(); - let after_parent_post_delete = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap() - .unwrap(); + Post::delete(pool, inserted_post.id).await?; + let after_parent_post_delete = CommunityAggregates::read(pool, inserted_community.id).await?; assert_eq!(0, after_parent_post_delete.comments); assert_eq!(0, after_parent_post_delete.posts); // Remove the 2nd person - Person::delete(pool, another_inserted_person.id) - .await - .unwrap(); - let after_person_delete = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap() - .unwrap(); + Person::delete(pool, another_inserted_person.id).await?; + let after_person_delete = CommunityAggregates::read(pool, inserted_community.id).await?; assert_eq!(1, after_person_delete.subscribers); assert_eq!(1, after_person_delete.subscribers_local); // This should delete all the associated rows, and fire triggers - let person_num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + let person_num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, person_num_deleted); // Delete the community - let community_num_deleted = Community::delete(pool, inserted_community.id) - .await - .unwrap(); + let community_num_deleted = Community::delete(pool, inserted_community.id).await?; assert_eq!(1, community_num_deleted); - let another_community_num_deleted = Community::delete(pool, another_inserted_community.id) - .await - .unwrap(); + let another_community_num_deleted = + Community::delete(pool, another_inserted_community.id).await?; assert_eq!(1, another_community_num_deleted); // Should be none found, since the creator was deleted - let after_delete = CommunityAggregates::read(pool, inserted_community.id) - .await - .unwrap(); - assert!(after_delete.is_none()); + let after_delete = CommunityAggregates::read(pool, inserted_community.id).await; + assert!(after_delete.is_err()); + + Ok(()) } } diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index a8767895c..6e0eacc07 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -1,4 +1,3 @@ -pub(crate) use crate::diesel::OptionalExtension; use crate::{ aggregates::structs::PersonAggregates, newtypes::PersonId, @@ -9,19 +8,13 @@ use diesel::{result::Error, QueryDsl}; use diesel_async::RunQueryDsl; impl PersonAggregates { - pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { let conn = &mut get_conn(pool).await?; - person_aggregates::table - .find(person_id) - .first(conn) - .await - .optional() + person_aggregates::table.find(person_id).first(conn).await } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -36,93 +29,81 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_user_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_user_agg"); - let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); + let another_inserted_person = Person::create(pool, &another_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL_site_agg".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_site_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); - 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".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); - - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; let post_like = PostLikeForm { post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; + let _inserted_post_like = PostLike::like(pool, &post_like).await?; - let _inserted_post_like = PostLike::like(pool, &post_like).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_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; let mut comment_like = CommentLikeForm { comment_id: inserted_comment.id, person_id: inserted_person.id, - post_id: inserted_post.id, score: 1, }; - let _inserted_comment_like = CommentLike::like(pool, &comment_like).await.unwrap(); - - let child_comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); + let _inserted_comment_like = CommentLike::like(pool, &comment_like).await?; + let child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; let child_comment_like = CommentLikeForm { comment_id: inserted_child_comment.id, person_id: another_inserted_person.id, - post_id: inserted_post.id, score: 1, }; - let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await.unwrap(); + let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await?; - let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(1, person_aggregates_before_delete.post_count); assert_eq!(1, person_aggregates_before_delete.post_score); @@ -130,13 +111,8 @@ mod tests { assert_eq!(2, person_aggregates_before_delete.comment_score); // Remove a post like - PostLike::remove(pool, inserted_person.id, inserted_post.id) - .await - .unwrap(); - let after_post_like_remove = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + PostLike::remove(pool, inserted_person.id, inserted_post.id).await?; + let after_post_like_remove = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(0, after_post_like_remove.post_score); Comment::update( @@ -147,8 +123,7 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; Comment::update( pool, inserted_child_comment.id, @@ -157,51 +132,34 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let after_parent_comment_removed = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + let after_parent_comment_removed = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(0, after_parent_comment_removed.comment_count); // TODO: fix person aggregate comment score calculation // assert_eq!(0, after_parent_comment_removed.comment_score); // Remove a parent comment (the scores should also be removed) - Comment::delete(pool, inserted_comment.id).await.unwrap(); - Comment::delete(pool, inserted_child_comment.id) - .await - .unwrap(); - let after_parent_comment_delete = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + Comment::delete(pool, inserted_comment.id).await?; + Comment::delete(pool, inserted_child_comment.id).await?; + let after_parent_comment_delete = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(0, after_parent_comment_delete.comment_count); // TODO: fix person aggregate comment score calculation // assert_eq!(0, after_parent_comment_delete.comment_score); // Add in the two comments again, then delete the post. - let new_parent_comment = Comment::create(pool, &comment_form, None).await.unwrap(); + let new_parent_comment = Comment::create(pool, &comment_form, None).await?; let _new_child_comment = - Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?; comment_like.comment_id = new_parent_comment.id; - CommentLike::like(pool, &comment_like).await.unwrap(); - let after_comment_add = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + CommentLike::like(pool, &comment_like).await?; + let after_comment_add = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(2, after_comment_add.comment_count); // TODO: fix person aggregate comment score calculation // assert_eq!(1, after_comment_add.comment_score); - Post::delete(pool, inserted_post.id).await.unwrap(); - let after_post_delete = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap() - .unwrap(); + Post::delete(pool, inserted_post.id).await?; + let after_post_delete = PersonAggregates::read(pool, inserted_person.id).await?; // TODO: fix person aggregate comment score calculation // assert_eq!(0, after_post_delete.comment_score); assert_eq!(0, after_post_delete.comment_count); @@ -209,24 +167,20 @@ mod tests { assert_eq!(0, after_post_delete.post_count); // This should delete all the associated rows, and fire triggers - let person_num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + let person_num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, person_num_deleted); - Person::delete(pool, another_inserted_person.id) - .await - .unwrap(); + Person::delete(pool, another_inserted_person.id).await?; // Delete the community - let community_num_deleted = Community::delete(pool, inserted_community.id) - .await - .unwrap(); + let community_num_deleted = Community::delete(pool, inserted_community.id).await?; assert_eq!(1, community_num_deleted); // Should be none found - let after_delete = PersonAggregates::read(pool, inserted_person.id) - .await - .unwrap(); - assert!(after_delete.is_none()); + let after_delete = PersonAggregates::read(pool, inserted_person.id).await; + assert!(after_delete.is_err()); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index eba3a02a3..b63017317 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -1,6 +1,5 @@ use crate::{ aggregates::structs::PostAggregates, - diesel::OptionalExtension, newtypes::PostId, schema::{community_aggregates, post, post_aggregates}, utils::{ @@ -13,13 +12,9 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; impl PostAggregates { - pub async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result { let conn = &mut get_conn(pool).await?; - post_aggregates::table - .find(post_id) - .first(conn) - .await - .optional() + post_aggregates::table.find(post_id).first(conn).await } pub async fn update_ranks(pool: &mut DbPool<'_>, post_id: PostId) -> Result { @@ -54,8 +49,6 @@ impl PostAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -70,62 +63,55 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg"); - let another_inserted_person = Person::create(pool, &another_person).await.unwrap(); + let another_inserted_person = Person::create(pool, &another_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL_community_agg".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_community_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).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.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 child_comment_form = CommentInsertForm::builder() - .content("A test comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); + let comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; + let child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; let post_like = PostLikeForm { post_id: inserted_post.id, @@ -133,12 +119,9 @@ mod tests { score: 1, }; - PostLike::like(pool, &post_like).await.unwrap(); + PostLike::like(pool, &post_like).await?; - let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(2, post_aggs_before_delete.comments); assert_eq!(1, post_aggs_before_delete.score); @@ -152,12 +135,9 @@ mod tests { score: -1, }; - PostLike::like(pool, &post_dislike).await.unwrap(); + PostLike::like(pool, &post_dislike).await?; - let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(2, post_aggs_after_dislike.comments); assert_eq!(0, post_aggs_after_dislike.score); @@ -165,95 +145,76 @@ mod tests { assert_eq!(1, post_aggs_after_dislike.downvotes); // Remove the comments - Comment::delete(pool, inserted_comment.id).await.unwrap(); - Comment::delete(pool, inserted_child_comment.id) - .await - .unwrap(); - let after_comment_delete = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + Comment::delete(pool, inserted_comment.id).await?; + Comment::delete(pool, inserted_child_comment.id).await?; + let after_comment_delete = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(0, after_comment_delete.comments); assert_eq!(0, after_comment_delete.score); assert_eq!(1, after_comment_delete.upvotes); assert_eq!(1, after_comment_delete.downvotes); // Remove the first post like - PostLike::remove(pool, inserted_person.id, inserted_post.id) - .await - .unwrap(); - let after_like_remove = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + PostLike::remove(pool, inserted_person.id, inserted_post.id).await?; + let after_like_remove = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(0, after_like_remove.comments); assert_eq!(-1, after_like_remove.score); assert_eq!(0, after_like_remove.upvotes); assert_eq!(1, after_like_remove.downvotes); // This should delete all the associated rows, and fire triggers - Person::delete(pool, another_inserted_person.id) - .await - .unwrap(); - let person_num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + Person::delete(pool, another_inserted_person.id).await?; + let person_num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, person_num_deleted); // Delete the community - let community_num_deleted = Community::delete(pool, inserted_community.id) - .await - .unwrap(); + let community_num_deleted = Community::delete(pool, inserted_community.id).await?; assert_eq!(1, community_num_deleted); // Should be none found, since the creator was deleted - let after_delete = PostAggregates::read(pool, inserted_post.id).await.unwrap(); - assert!(after_delete.is_none()); + let after_delete = PostAggregates::read(pool, inserted_post.id).await; + assert!(after_delete.is_err()); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_soft_delete() { + async fn test_soft_delete() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL_community_agg".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_community_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; - let new_post = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); + let comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let inserted_comment = Comment::create(pool, &comment_form, None).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.unwrap(); - - let post_aggregates_before = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggregates_before = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(1, post_aggregates_before.comments); Comment::update( @@ -264,13 +225,9 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let post_aggregates_after_remove = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggregates_after_remove = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(0, post_aggregates_after_remove.comments); Comment::update( @@ -281,8 +238,7 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; Comment::update( pool, @@ -292,13 +248,9 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let post_aggregates_after_delete = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggregates_after_delete = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(0, post_aggregates_after_delete.comments); Comment::update( @@ -309,21 +261,17 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let post_aggregates_after_delete_remove = PostAggregates::read(pool, inserted_post.id) - .await - .unwrap() - .unwrap(); + let post_aggregates_after_delete_remove = PostAggregates::read(pool, inserted_post.id).await?; assert_eq!(0, post_aggregates_after_delete_remove.comments); - Comment::delete(pool, inserted_comment.id).await.unwrap(); - Post::delete(pool, inserted_post.id).await.unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Comment::delete(pool, inserted_comment.id).await?; + Post::delete(pool, inserted_post.id).await?; + Person::delete(pool, inserted_person.id).await?; + Community::delete(pool, inserted_community.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index ee9a1be9c..379ddd2d9 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -1,6 +1,5 @@ use crate::{ aggregates::structs::SiteAggregates, - diesel::OptionalExtension, schema::site_aggregates, utils::{get_conn, DbPool}, }; @@ -8,15 +7,13 @@ use diesel::result::Error; use diesel_async::RunQueryDsl; impl SiteAggregates { - pub async fn read(pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>) -> Result { let conn = &mut get_conn(pool).await?; - site_aggregates::table.first(conn).await.optional() + site_aggregates::table.first(conn).await } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -32,83 +29,76 @@ mod tests { traits::Crud, utils::{build_db_pool_for_tests, DbPool}, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; async fn prepare_site_with_community( pool: &mut DbPool<'_>, - ) -> (Instance, Person, Site, Community) { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + ) -> Result<(Instance, Person, Site, Community), Error> { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_site_agg"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; - let site_form = SiteInsertForm::builder() - .name("test_site".into()) - .instance_id(inserted_instance.id) - .build(); + let site_form = SiteInsertForm::new("test_site".into(), inserted_instance.id); + let inserted_site = Site::create(pool, &site_form).await?; - let inserted_site = Site::create(pool, &site_form).await.unwrap(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL_site_agg".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); - let new_community = CommunityInsertForm::builder() - .name("TIL_site_agg".into()) - .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 inserted_community = Community::create(pool, &new_community).await.unwrap(); - ( + Ok(( inserted_instance, inserted_person, inserted_site, inserted_community, - ) + )) } #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); let (inserted_instance, inserted_person, inserted_site, inserted_community) = - prepare_site_with_community(pool).await; + prepare_site_with_community(pool).await?; - let new_post = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); // Insert two of those posts - let inserted_post = Post::create(pool, &new_post).await.unwrap(); - let _inserted_post_again = Post::create(pool, &new_post).await.unwrap(); + let inserted_post = Post::create(pool, &new_post).await?; + let _inserted_post_again = 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 comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); // Insert two of those comments - let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); - - let child_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 child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let _inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; - let site_aggregates_before_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); + let site_aggregates_before_delete = SiteAggregates::read(pool).await?; // TODO: this is unstable, sometimes it returns 0 users, sometimes 1 //assert_eq!(0, site_aggregates_before_delete.users); @@ -117,42 +107,42 @@ mod tests { assert_eq!(2, site_aggregates_before_delete.comments); // Try a post delete - Post::delete(pool, inserted_post.id).await.unwrap(); - let site_aggregates_after_post_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); + Post::delete(pool, inserted_post.id).await?; + let site_aggregates_after_post_delete = SiteAggregates::read(pool).await?; assert_eq!(1, site_aggregates_after_post_delete.posts); assert_eq!(0, site_aggregates_after_post_delete.comments); // This shouuld delete all the associated rows, and fire triggers - let person_num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); + let person_num_deleted = Person::delete(pool, inserted_person.id).await?; assert_eq!(1, person_num_deleted); // Delete the community - let community_num_deleted = Community::delete(pool, inserted_community.id) - .await - .unwrap(); + let community_num_deleted = Community::delete(pool, inserted_community.id).await?; assert_eq!(1, community_num_deleted); // Site should still exist, it can without a site creator. let after_delete_creator = SiteAggregates::read(pool).await; assert!(after_delete_creator.is_ok()); - Site::delete(pool, inserted_site.id).await.unwrap(); - let after_delete_site = SiteAggregates::read(pool).await.unwrap(); - assert!(after_delete_site.is_none()); + Site::delete(pool, inserted_site.id).await?; + let after_delete_site = SiteAggregates::read(pool).await; + assert!(after_delete_site.is_err()); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_soft_delete() { + async fn test_soft_delete() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); let (inserted_instance, inserted_person, inserted_site, inserted_community) = - prepare_site_with_community(pool).await; + prepare_site_with_community(pool).await?; - let site_aggregates_before = SiteAggregates::read(pool).await.unwrap().unwrap(); + let site_aggregates_before = SiteAggregates::read(pool).await?; assert_eq!(1, site_aggregates_before.communities); Community::update( @@ -163,10 +153,9 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let site_aggregates_after_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); + let site_aggregates_after_delete = SiteAggregates::read(pool).await?; assert_eq!(0, site_aggregates_after_delete.communities); Community::update( @@ -177,8 +166,7 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; Community::update( pool, @@ -188,10 +176,9 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let site_aggregates_after_remove = SiteAggregates::read(pool).await.unwrap().unwrap(); + let site_aggregates_after_remove = SiteAggregates::read(pool).await?; assert_eq!(0, site_aggregates_after_remove.communities); Community::update( @@ -202,17 +189,16 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; - let site_aggregates_after_remove_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); + let site_aggregates_after_remove_delete = SiteAggregates::read(pool).await?; assert_eq!(0, site_aggregates_after_remove_delete.communities); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Site::delete(pool, inserted_site.id).await.unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Community::delete(pool, inserted_community.id).await?; + Site::delete(pool, inserted_site.id).await?; + Person::delete(pool, inserted_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/impls/activity.rs b/crates/db_schema/src/impls/activity.rs index 9391d55bc..fff0c2f0c 100644 --- a/crates/db_schema/src/impls/activity.rs +++ b/crates/db_schema/src/impls/activity.rs @@ -22,22 +22,15 @@ impl SentActivity { .await } - pub async fn read_from_apub_id( - pool: &mut DbPool<'_>, - object_id: &DbUrl, - ) -> Result, Error> { + pub async fn read_from_apub_id(pool: &mut DbPool<'_>, object_id: &DbUrl) -> Result { use crate::schema::sent_activity::dsl::{ap_id, sent_activity}; let conn = &mut get_conn(pool).await?; - sent_activity - .filter(ap_id.eq(object_id)) - .first(conn) - .await - .optional() + sent_activity.filter(ap_id.eq(object_id)).first(conn).await } - pub async fn read(pool: &mut DbPool<'_>, object_id: ActivityId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, object_id: ActivityId) -> Result { use crate::schema::sent_activity::dsl::sent_activity; let conn = &mut get_conn(pool).await?; - sent_activity.find(object_id).first(conn).await.optional() + sent_activity.find(object_id).first(conn).await } } @@ -65,12 +58,11 @@ impl ReceivedActivity { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; use crate::{source::activity::ActorType, utils::build_db_pool_for_tests}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serde_json::json; use serial_test::serial; @@ -78,26 +70,25 @@ mod tests { #[tokio::test] #[serial] - async fn receive_activity_duplicate() { + async fn receive_activity_duplicate() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let ap_id: DbUrl = Url::parse("http://example.com/activity/531") - .unwrap() - .into(); + let ap_id: DbUrl = Url::parse("http://example.com/activity/531")?.into(); // inserting activity should only work once - ReceivedActivity::create(pool, &ap_id).await.unwrap(); - ReceivedActivity::create(pool, &ap_id).await.unwrap_err(); + ReceivedActivity::create(pool, &ap_id).await?; + let second = ReceivedActivity::create(pool, &ap_id).await; + assert!(second.is_err()); + + Ok(()) } #[tokio::test] #[serial] - async fn sent_activity_write_read() { + async fn sent_activity_write_read() -> LemmyResult<()> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let ap_id: DbUrl = Url::parse("http://example.com/activity/412") - .unwrap() - .into(); + let ap_id: DbUrl = Url::parse("http://example.com/activity/412")?.into(); let data = json!({ "key1": "0xF9BA143B95FF6D82", "key2": "42", @@ -108,23 +99,20 @@ mod tests { ap_id: ap_id.clone(), data: data.clone(), sensitive, - actor_apub_id: Url::parse("http://example.com/u/exampleuser") - .unwrap() - .into(), + actor_apub_id: Url::parse("http://example.com/u/exampleuser")?.into(), actor_type: ActorType::Person, send_all_instances: false, send_community_followers_of: None, send_inboxes: vec![], }; - SentActivity::create(pool, form).await.unwrap(); + SentActivity::create(pool, form).await?; - let res = SentActivity::read_from_apub_id(pool, &ap_id) - .await - .unwrap() - .unwrap(); + let res = SentActivity::read_from_apub_id(pool, &ap_id).await?; assert_eq!(res.ap_id, ap_id); assert_eq!(res.data, data); assert_eq!(res.sensitive, sensitive); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index 5a8658baf..bff729f41 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -199,26 +199,22 @@ impl CommunityLanguage { /// Returns true if the given language is one of configured languages for given community pub async fn is_allowed_community_language( pool: &mut DbPool<'_>, - for_language_id: Option, + for_language_id: LanguageId, for_community_id: CommunityId, ) -> LemmyResult<()> { use crate::schema::community_language::dsl::community_language; let conn = &mut get_conn(pool).await?; - if let Some(for_language_id) = for_language_id { - let is_allowed = select(exists( - community_language.find((for_community_id, for_language_id)), - )) - .get_result(conn) - .await?; + let is_allowed = select(exists( + community_language.find((for_community_id, for_language_id)), + )) + .get_result(conn) + .await?; - if is_allowed { - Ok(()) - } else { - Err(LemmyErrorType::LanguageNotAllowed)? - } - } else { + if is_allowed { Ok(()) + } else { + Err(LemmyErrorType::LanguageNotAllowed)? } } @@ -327,7 +323,7 @@ pub async fn default_post_language( pool: &mut DbPool<'_>, community_id: CommunityId, local_user_id: LocalUserId, -) -> Result, Error> { +) -> Result { use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul}; let conn = &mut get_conn(pool).await?; let mut intersection = ul::local_user_language @@ -339,12 +335,12 @@ pub async fn default_post_language( .await?; if intersection.len() == 1 { - Ok(intersection.pop()) + Ok(intersection.pop().unwrap_or(UNDETERMINED_ID)) } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) { intersection.retain(|i| i != &UNDETERMINED_ID); - Ok(intersection.pop()) + Ok(intersection.pop().unwrap_or(UNDETERMINED_ID)) } else { - Ok(None) + Ok(UNDETERMINED_ID) } } @@ -392,8 +388,7 @@ async fn convert_read_languages( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; @@ -409,284 +404,238 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; - async fn test_langs1(pool: &mut DbPool<'_>) -> Vec { - vec![ - Language::read_id_from_code(pool, Some("en")) - .await - .unwrap() - .unwrap(), - Language::read_id_from_code(pool, Some("fr")) - .await - .unwrap() - .unwrap(), - Language::read_id_from_code(pool, Some("ru")) - .await - .unwrap() - .unwrap(), - ] + async fn test_langs1(pool: &mut DbPool<'_>) -> Result, Error> { + Ok(vec![ + Language::read_id_from_code(pool, "en").await?, + Language::read_id_from_code(pool, "fr").await?, + Language::read_id_from_code(pool, "ru").await?, + ]) } - async fn test_langs2(pool: &mut DbPool<'_>) -> Vec { - vec![ - Language::read_id_from_code(pool, Some("fi")) - .await - .unwrap() - .unwrap(), - Language::read_id_from_code(pool, Some("se")) - .await - .unwrap() - .unwrap(), - ] + async fn test_langs2(pool: &mut DbPool<'_>) -> Result, Error> { + Ok(vec![ + Language::read_id_from_code(pool, "fi").await?, + Language::read_id_from_code(pool, "se").await?, + ]) } - async fn create_test_site(pool: &mut DbPool<'_>) -> (Site, Instance) { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + async fn create_test_site(pool: &mut DbPool<'_>) -> Result<(Site, Instance), Error> { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; - let site_form = SiteInsertForm::builder() - .name("test site".to_string()) - .instance_id(inserted_instance.id) - .build(); - let site = Site::create(pool, &site_form).await.unwrap(); + let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id); + let site = Site::create(pool, &site_form).await?; // Create a local site, since this is necessary for local languages - let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build(); - LocalSite::create(pool, &local_site_form).await.unwrap(); + let local_site_form = LocalSiteInsertForm::new(site.id); + LocalSite::create(pool, &local_site_form).await?; - (site, inserted_instance) + Ok((site, inserted_instance)) } #[tokio::test] #[serial] - async fn test_convert_update_languages() { + async fn test_convert_update_languages() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); // call with empty vec, returns all languages - let conn = &mut get_conn(pool).await.unwrap(); - let converted1 = convert_update_languages(conn, vec![]).await.unwrap(); + let conn = &mut get_conn(pool).await?; + let converted1 = convert_update_languages(conn, vec![]).await?; assert_eq!(184, converted1.len()); // call with nonempty vec, returns same vec - let test_langs = test_langs1(&mut conn.into()).await; - let converted2 = convert_update_languages(conn, test_langs.clone()) - .await - .unwrap(); + let test_langs = test_langs1(&mut conn.into()).await?; + let converted2 = convert_update_languages(conn, test_langs.clone()).await?; assert_eq!(test_langs, converted2); + + Ok(()) } #[tokio::test] #[serial] - async fn test_convert_read_languages() { + async fn test_convert_read_languages() -> Result<(), Error> { use crate::schema::language::dsl::{id, language}; let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); // call with all languages, returns empty vec - let conn = &mut get_conn(pool).await.unwrap(); - let all_langs = language.select(id).get_results(conn).await.unwrap(); - let converted1: Vec = convert_read_languages(conn, all_langs).await.unwrap(); + let conn = &mut get_conn(pool).await?; + let all_langs = language.select(id).get_results(conn).await?; + let converted1: Vec = convert_read_languages(conn, all_langs).await?; assert_eq!(0, converted1.len()); // call with nonempty vec, returns same vec - let test_langs = test_langs1(&mut conn.into()).await; - let converted2 = convert_read_languages(conn, test_langs.clone()) - .await - .unwrap(); + let test_langs = test_langs1(&mut conn.into()).await?; + let converted2 = convert_read_languages(conn, test_langs.clone()).await?; assert_eq!(test_langs, converted2); + + Ok(()) } #[tokio::test] #[serial] - async fn test_site_languages() { + async fn test_site_languages() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (site, instance) = create_test_site(pool).await; - let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap(); + let (site, instance) = create_test_site(pool).await?; + let site_languages1 = SiteLanguage::read_local_raw(pool).await?; // site is created with all languages assert_eq!(184, site_languages1.len()); - let test_langs = test_langs1(pool).await; - SiteLanguage::update(pool, test_langs.clone(), &site) - .await - .unwrap(); + let test_langs = test_langs1(pool).await?; + SiteLanguage::update(pool, test_langs.clone(), &site).await?; - let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap(); + let site_languages2 = SiteLanguage::read_local_raw(pool).await?; // after update, site only has new languages assert_eq!(test_langs, site_languages2); - Site::delete(pool, site.id).await.unwrap(); - Instance::delete(pool, instance.id).await.unwrap(); - LocalSite::delete(pool).await.unwrap(); + Site::delete(pool, site.id).await?; + Instance::delete(pool, instance.id).await?; + LocalSite::delete(pool).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_user_languages() { + async fn test_user_languages() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (site, instance) = create_test_site(pool).await; + let (site, instance) = create_test_site(pool).await?; let person_form = PersonInsertForm::test_form(instance.id, "my test person"); - let person = Person::create(pool, &person_form).await.unwrap(); + let person = Person::create(pool, &person_form).await?; let local_user_form = LocalUserInsertForm::test_form(person.id); - let local_user = LocalUser::create(pool, &local_user_form, vec![]) - .await - .unwrap(); - let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap(); + let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; + let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await?; // new user should be initialized with all languages assert_eq!(0, local_user_langs1.len()); // update user languages - let test_langs2 = test_langs2(pool).await; - LocalUserLanguage::update(pool, test_langs2, local_user.id) - .await - .unwrap(); - let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap(); + let test_langs2 = test_langs2(pool).await?; + LocalUserLanguage::update(pool, test_langs2, local_user.id).await?; + let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await?; assert_eq!(3, local_user_langs2.len()); - Person::delete(pool, person.id).await.unwrap(); - LocalUser::delete(pool, local_user.id).await.unwrap(); - Site::delete(pool, site.id).await.unwrap(); - LocalSite::delete(pool).await.unwrap(); - Instance::delete(pool, instance.id).await.unwrap(); + Person::delete(pool, person.id).await?; + LocalUser::delete(pool, local_user.id).await?; + Site::delete(pool, site.id).await?; + LocalSite::delete(pool).await?; + Instance::delete(pool, instance.id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_community_languages() { + async fn test_community_languages() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (site, instance) = create_test_site(pool).await; - let test_langs = test_langs1(pool).await; - SiteLanguage::update(pool, test_langs.clone(), &site) - .await - .unwrap(); + let (site, instance) = create_test_site(pool).await?; + let test_langs = test_langs1(pool).await?; + SiteLanguage::update(pool, test_langs.clone(), &site).await?; - let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap(); + let read_site_langs = SiteLanguage::read(pool, site.id).await?; assert_eq!(test_langs, read_site_langs); // Test the local ones are the same - let read_local_site_langs = SiteLanguage::read_local_raw(pool).await.unwrap(); + let read_local_site_langs = SiteLanguage::read_local_raw(pool).await?; assert_eq!(test_langs, read_local_site_langs); - let community_form = CommunityInsertForm::builder() - .name("test community".to_string()) - .title("test community".to_string()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); - let community = Community::create(pool, &community_form).await.unwrap(); - let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap(); + let community_form = CommunityInsertForm::new( + instance.id, + "test community".to_string(), + "test community".to_string(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; + let community_langs1 = CommunityLanguage::read(pool, community.id).await?; // community is initialized with site languages assert_eq!(test_langs, community_langs1); let allowed_lang1 = - CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id) - .await; + CommunityLanguage::is_allowed_community_language(pool, test_langs[0], community.id).await; assert!(allowed_lang1.is_ok()); - let test_langs2 = test_langs2(pool).await; + let test_langs2 = test_langs2(pool).await?; let allowed_lang2 = - CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id) - .await; + CommunityLanguage::is_allowed_community_language(pool, test_langs2[0], community.id).await; assert!(allowed_lang2.is_err()); // limit site languages to en, fi. after this, community languages should be updated to // intersection of old languages (en, fr, ru) and (en, fi), which is only fi. - SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site) - .await - .unwrap(); - let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap(); + SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site).await?; + let community_langs2 = CommunityLanguage::read(pool, community.id).await?; assert_eq!(vec![test_langs[0]], community_langs2); // update community languages to different ones - CommunityLanguage::update(pool, test_langs2.clone(), community.id) - .await - .unwrap(); - let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap(); + CommunityLanguage::update(pool, test_langs2.clone(), community.id).await?; + let community_langs3 = CommunityLanguage::read(pool, community.id).await?; assert_eq!(test_langs2, community_langs3); - Community::delete(pool, community.id).await.unwrap(); - Site::delete(pool, site.id).await.unwrap(); - LocalSite::delete(pool).await.unwrap(); - Instance::delete(pool, instance.id).await.unwrap(); + Community::delete(pool, community.id).await?; + Site::delete(pool, site.id).await?; + LocalSite::delete(pool).await?; + Instance::delete(pool, instance.id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_default_post_language() { + async fn test_default_post_language() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (site, instance) = create_test_site(pool).await; - let test_langs = test_langs1(pool).await; - let test_langs2 = test_langs2(pool).await; + let (site, instance) = create_test_site(pool).await?; + let test_langs = test_langs1(pool).await?; + let test_langs2 = test_langs2(pool).await?; - let community_form = CommunityInsertForm::builder() - .name("test community".to_string()) - .title("test community".to_string()) - .public_key("pubkey".to_string()) - .instance_id(instance.id) - .build(); - let community = Community::create(pool, &community_form).await.unwrap(); - CommunityLanguage::update(pool, test_langs, community.id) - .await - .unwrap(); + let community_form = CommunityInsertForm::new( + instance.id, + "test community".to_string(), + "test community".to_string(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; + CommunityLanguage::update(pool, test_langs, community.id).await?; let person_form = PersonInsertForm::test_form(instance.id, "my test person"); - let person = Person::create(pool, &person_form).await.unwrap(); + let person = Person::create(pool, &person_form).await?; let local_user_form = LocalUserInsertForm::test_form(person.id); - let local_user = LocalUser::create(pool, &local_user_form, vec![]) - .await - .unwrap(); - LocalUserLanguage::update(pool, test_langs2, local_user.id) - .await - .unwrap(); + let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; + LocalUserLanguage::update(pool, test_langs2, local_user.id).await?; // no overlap in user/community languages, so defaults to undetermined - let def1 = default_post_language(pool, community.id, local_user.id) - .await - .unwrap(); - assert_eq!(None, def1); + let def1 = default_post_language(pool, community.id, local_user.id).await?; + assert_eq!(UNDETERMINED_ID, def1); - let ru = Language::read_id_from_code(pool, Some("ru")) - .await - .unwrap() - .unwrap(); + let ru = Language::read_id_from_code(pool, "ru").await?; let test_langs3 = vec![ ru, - Language::read_id_from_code(pool, Some("fi")) - .await - .unwrap() - .unwrap(), - Language::read_id_from_code(pool, Some("se")) - .await - .unwrap() - .unwrap(), + Language::read_id_from_code(pool, "fi").await?, + Language::read_id_from_code(pool, "se").await?, UNDETERMINED_ID, ]; - LocalUserLanguage::update(pool, test_langs3, local_user.id) - .await - .unwrap(); + LocalUserLanguage::update(pool, test_langs3, local_user.id).await?; // this time, both have ru as common lang - let def2 = default_post_language(pool, community.id, local_user.id) - .await - .unwrap(); - assert_eq!(Some(ru), def2); + let def2 = default_post_language(pool, community.id, local_user.id).await?; + assert_eq!(ru, def2); - Person::delete(pool, person.id).await.unwrap(); - Community::delete(pool, community.id).await.unwrap(); - LocalUser::delete(pool, local_user.id).await.unwrap(); - Site::delete(pool, site.id).await.unwrap(); - LocalSite::delete(pool).await.unwrap(); - Instance::delete(pool, instance.id).await.unwrap(); + Person::delete(pool, person.id).await?; + Community::delete(pool, community.id).await?; + LocalUser::delete(pool, local_user.id).await?; + Site::delete(pool, site.id).await?; + LocalSite::delete(pool).await?; + Instance::delete(pool, instance.id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/impls/captcha_answer.rs b/crates/db_schema/src/impls/captcha_answer.rs index 1d0604c0a..d7183e4fb 100644 --- a/crates/db_schema/src/impls/captcha_answer.rs +++ b/crates/db_schema/src/impls/captcha_answer.rs @@ -13,6 +13,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CaptchaAnswer { pub async fn insert(pool: &mut DbPool<'_>, captcha: &CaptchaAnswerForm) -> Result { @@ -27,7 +28,7 @@ impl CaptchaAnswer { pub async fn check_captcha( pool: &mut DbPool<'_>, to_check: CheckCaptchaAnswer, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; // fetch requested captcha @@ -43,13 +44,13 @@ impl CaptchaAnswer { .execute(conn) .await?; - Ok(captcha_exists) + captcha_exists + .then_some(()) + .ok_or(LemmyErrorType::CaptchaIncorrect.into()) } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -83,7 +84,6 @@ mod tests { .await; assert!(result.is_ok()); - assert!(result.unwrap()); } #[tokio::test] @@ -119,7 +119,6 @@ mod tests { ) .await; - assert!(result_repeat.is_ok()); - assert!(!result_repeat.unwrap()); + assert!(result_repeat.is_err()); } } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 977bc9083..30d18465f 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -40,12 +40,12 @@ impl Comment { pub async fn update_removed_for_creator( pool: &mut DbPool<'_>, for_creator_id: PersonId, - new_removed: bool, + removed: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; diesel::update(comment::table.filter(comment::creator_id.eq(for_creator_id))) .set(( - comment::removed.eq(new_removed), + comment::removed.eq(removed), comment::updated.eq(naive_now()), )) .get_results::(conn) @@ -196,8 +196,6 @@ impl Saveable for CommentSaved { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -221,48 +219,44 @@ mod tests { utils::build_db_pool_for_tests, }; use diesel_ltree::Ltree; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; use url::Url; #[tokio::test] #[serial] - async fn test_crud() { + 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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "terry"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; - let new_community = CommunityInsertForm::builder() - .name("test community".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).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.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_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; let expected_comment = Comment { id: inserted_comment.id, @@ -277,38 +271,32 @@ mod tests { ap_id: Url::parse(&format!( "https://lemmy-alpha/comment/{}", inserted_comment.id - )) - .unwrap() + ))? .into(), distinguished: false, local: true, language_id: LanguageId::default(), }; - let child_comment_form = CommentInsertForm::builder() - .content("A child comment".into()) - .creator_id(inserted_person.id) - .post_id(inserted_post.id) - .build(); - + let child_comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A child comment".into(), + ); let inserted_child_comment = - Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)) - .await - .unwrap(); + Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; // Comment Like let comment_like_form = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, 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 expected_comment_like = CommentLike { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, published: inserted_comment_like.published, score: 1, @@ -320,7 +308,7 @@ mod tests { person_id: inserted_person.id, }; - let inserted_comment_saved = CommentSaved::save(pool, &comment_saved_form).await.unwrap(); + let inserted_comment_saved = CommentSaved::save(pool, &comment_saved_form).await?; let expected_comment_saved = CommentSaved { comment_id: inserted_comment.id, @@ -333,30 +321,17 @@ mod tests { ..Default::default() }; - let updated_comment = Comment::update(pool, inserted_comment.id, &comment_update_form) - .await - .unwrap(); + let updated_comment = Comment::update(pool, inserted_comment.id, &comment_update_form).await?; - let read_comment = Comment::read(pool, inserted_comment.id) - .await - .unwrap() - .unwrap(); - let like_removed = CommentLike::remove(pool, inserted_person.id, inserted_comment.id) - .await - .unwrap(); - let saved_removed = CommentSaved::unsave(pool, &comment_saved_form) - .await - .unwrap(); - let num_deleted = Comment::delete(pool, inserted_comment.id).await.unwrap(); - Comment::delete(pool, inserted_child_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(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + let read_comment = Comment::read(pool, inserted_comment.id).await?; + let like_removed = CommentLike::remove(pool, inserted_person.id, inserted_comment.id).await?; + let saved_removed = CommentSaved::unsave(pool, &comment_saved_form).await?; + let num_deleted = Comment::delete(pool, inserted_comment.id).await?; + Comment::delete(pool, inserted_child_comment.id).await?; + Post::delete(pool, inserted_post.id).await?; + Community::delete(pool, inserted_community.id).await?; + Person::delete(pool, inserted_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(expected_comment, read_comment); assert_eq!(expected_comment, inserted_comment); @@ -370,5 +345,7 @@ mod tests { assert_eq!(1, like_removed); assert_eq!(1, saved_removed); assert_eq!(1, num_deleted); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index eaf35a90d..355979264 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -30,13 +30,13 @@ use crate::{ get_conn, DbPool, }, + ListingType, SubscribedType, }; use chrono::{DateTime, Utc}; use diesel::{ deserialize, - dsl, - dsl::{exists, insert_into}, + dsl::{self, exists, insert_into, not}, pg::Pg, result::Error, select, @@ -152,26 +152,24 @@ impl Community { pub async fn get_by_collection_url( pool: &mut DbPool<'_>, url: &DbUrl, - ) -> Result, Error> { + ) -> LemmyResult<(Community, CollectionType)> { let conn = &mut get_conn(pool).await?; let res = community::table .filter(community::moderators_url.eq(url)) .first(conn) - .await - .optional()?; + .await; - if let Some(c) = res { - Ok(Some((c, CollectionType::Moderators))) + if let Ok(c) = res { + Ok((c, CollectionType::Moderators)) } else { let res = community::table .filter(community::featured_url.eq(url)) .first(conn) - .await - .optional()?; - if let Some(c) = res { - Ok(Some((c, CollectionType::Featured))) + .await; + if let Ok(c) = res { + Ok((c, CollectionType::Featured)) } else { - Ok(None) + Err(LemmyErrorType::NotFound.into()) } } } @@ -196,6 +194,30 @@ impl Community { .await?; Ok(()) } + + pub async fn get_random_community_id( + pool: &mut DbPool<'_>, + type_: &Option, + ) -> Result { + let conn = &mut get_conn(pool).await?; + sql_function!(fn random() -> Text); + + let mut query = community::table + .filter(not(community::deleted)) + .filter(not(community::removed)) + .into_boxed(); + + if let Some(ListingType::Local) = type_ { + query = query.filter(community::local); + } + + query + .select(community::id) + .order(random()) + .limit(1) + .first::(conn) + .await + } } impl CommunityModerator { @@ -322,16 +344,18 @@ impl CommunityFollower { /// Check if a remote instance has any followers on local instance. For this it is enough to check /// if any follow relation is stored. Dont use this for local community. - pub async fn has_local_followers( + pub async fn check_has_local_followers( pool: &mut DbPool<'_>, remote_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; select(exists(community_follower::table.filter( community_follower::community_id.eq(remote_community_id), ))) - .get_result(conn) - .await + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityHasNoFollowers.into()) } } @@ -432,7 +456,6 @@ impl ApubActor for Community { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ source::{ @@ -455,7 +478,7 @@ mod tests { utils::build_db_pool_for_tests, CommunityVisibility, }; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -473,13 +496,12 @@ mod tests { let artemis_person = PersonInsertForm::test_form(inserted_instance.id, "artemis"); let inserted_artemis = Person::create(pool, &artemis_person).await?; - let new_community = CommunityInsertForm::builder() - .name("TIL".into()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "TIL".into(), + "nada".to_owned(), + "pubkey".to_string(), + ); let inserted_community = Community::create(pool, &new_community).await?; let expected_community = Community { @@ -501,7 +523,6 @@ mod tests { banner: None, followers_url: inserted_community.followers_url.clone(), inbox_url: inserted_community.inbox_url.clone(), - shared_inbox_url: None, moderators_url: None, featured_url: None, hidden: false, @@ -595,9 +616,7 @@ mod tests { expires: None, }; - let read_community = Community::read(pool, inserted_community.id) - .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let read_community = Community::read(pool, inserted_community.id).await?; let update_community_form = CommunityUpdateForm { title: Some("nada".to_owned()), diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 944a53ae3..cd541cd8b 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -9,26 +9,29 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( community_block::table.find((for_person_id, for_community_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_schema/src/impls/custom_emoji.rs b/crates/db_schema/src/impls/custom_emoji.rs index 050301659..9ba359071 100644 --- a/crates/db_schema/src/impls/custom_emoji.rs +++ b/crates/db_schema/src/impls/custom_emoji.rs @@ -8,36 +8,37 @@ use crate::{ custom_emoji::{CustomEmoji, CustomEmojiInsertForm, CustomEmojiUpdateForm}, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, }, + traits::Crud, utils::{get_conn, DbPool}, }; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; -impl CustomEmoji { - pub async fn create(pool: &mut DbPool<'_>, form: &CustomEmojiInsertForm) -> Result { +#[async_trait] +impl Crud for CustomEmoji { + type InsertForm = CustomEmojiInsertForm; + type UpdateForm = CustomEmojiUpdateForm; + type IdType = CustomEmojiId; + + async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; insert_into(custom_emoji) .values(form) .get_result::(conn) .await } - pub async fn update( + + async fn update( pool: &mut DbPool<'_>, - emoji_id: CustomEmojiId, - form: &CustomEmojiUpdateForm, + emoji_id: Self::IdType, + new_custom_emoji: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(custom_emoji.find(emoji_id)) - .set(form) + .set(new_custom_emoji) .get_result::(conn) .await } - pub async fn delete(pool: &mut DbPool<'_>, emoji_id: CustomEmojiId) -> Result { - let conn = &mut get_conn(pool).await?; - diesel::delete(custom_emoji.find(emoji_id)) - .execute(conn) - .await - } } impl CustomEmojiKeyword { diff --git a/crates/db_schema/src/impls/email_verification.rs b/crates/db_schema/src/impls/email_verification.rs index b4951cf73..39c7fe0bc 100644 --- a/crates/db_schema/src/impls/email_verification.rs +++ b/crates/db_schema/src/impls/email_verification.rs @@ -16,7 +16,6 @@ use diesel::{ sql_types::Timestamptz, ExpressionMethods, IntoSql, - OptionalExtension, QueryDsl, }; use diesel_async::RunQueryDsl; @@ -30,14 +29,13 @@ impl EmailVerification { .await } - pub async fn read_for_token(pool: &mut DbPool<'_>, token: &str) -> Result, Error> { + pub async fn read_for_token(pool: &mut DbPool<'_>, token: &str) -> Result { let conn = &mut get_conn(pool).await?; email_verification .filter(verification_token.eq(token)) .filter(published.gt(now.into_sql::() - 7.days())) .first(conn) .await - .optional() } pub async fn delete_old_tokens_for_local_user( pool: &mut DbPool<'_>, diff --git a/crates/db_schema/src/impls/federation_allowlist.rs b/crates/db_schema/src/impls/federation_allowlist.rs index 80408a526..cbfd14b03 100644 --- a/crates/db_schema/src/impls/federation_allowlist.rs +++ b/crates/db_schema/src/impls/federation_allowlist.rs @@ -48,20 +48,19 @@ impl FederationAllowList { } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ source::{federation_allowlist::FederationAllowList, instance::Instance}, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_allowlist_insert_and_clear() { + async fn test_allowlist_insert_and_clear() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); let domains = vec![ @@ -72,9 +71,9 @@ mod tests { let allowed = Some(domains.clone()); - FederationAllowList::replace(pool, allowed).await.unwrap(); + FederationAllowList::replace(pool, allowed).await?; - let allows = Instance::allowlist(pool).await.unwrap(); + let allows = Instance::allowlist(pool).await?; let allows_domains = allows .iter() .map(|i| i.domain.clone()) @@ -86,13 +85,13 @@ mod tests { // Now test clearing them via Some(empty vec) let clear_allows = Some(Vec::new()); - FederationAllowList::replace(pool, clear_allows) - .await - .unwrap(); - let allows = Instance::allowlist(pool).await.unwrap(); + FederationAllowList::replace(pool, clear_allows).await?; + let allows = Instance::allowlist(pool).await?; assert_eq!(0, allows.len()); - Instance::delete_all(pool).await.unwrap(); + Instance::delete_all(pool).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/impls/images.rs b/crates/db_schema/src/impls/images.rs index 547bfc4e2..8ded98e41 100644 --- a/crates/db_schema/src/impls/images.rs +++ b/crates/db_schema/src/impls/images.rs @@ -1,14 +1,7 @@ use crate::{ newtypes::DbUrl, schema::{image_details, local_image, remote_image}, - source::images::{ - ImageDetails, - ImageDetailsForm, - LocalImage, - LocalImageForm, - RemoteImage, - RemoteImageForm, - }, + source::images::{ImageDetails, ImageDetailsForm, LocalImage, LocalImageForm, RemoteImage}, utils::{get_conn, DbPool}, }; use diesel::{ @@ -20,7 +13,8 @@ use diesel::{ NotFound, QueryDsl, }; -use diesel_async::{AsyncPgConnection, RunQueryDsl}; +use diesel_async::RunQueryDsl; +use url::Url; impl LocalImage { pub async fn create( @@ -38,7 +32,7 @@ impl LocalImage { .get_result::(conn) .await; - ImageDetails::create(conn, image_details_form).await?; + ImageDetails::create(&mut conn.into(), image_details_form).await?; local_insert }) as _ @@ -60,26 +54,16 @@ impl LocalImage { } impl RemoteImage { - pub async fn create(pool: &mut DbPool<'_>, form: &ImageDetailsForm) -> Result { + pub async fn create(pool: &mut DbPool<'_>, links: Vec) -> Result { let conn = &mut get_conn(pool).await?; - conn - .build_transaction() - .run(|conn| { - Box::pin(async move { - let remote_image_form = RemoteImageForm { - link: form.link.clone(), - }; - let remote_insert = insert_into(remote_image::table) - .values(remote_image_form) - .on_conflict_do_nothing() - .execute(conn) - .await; - - ImageDetails::create(conn, form).await?; - - remote_insert - }) as _ - }) + let forms = links + .into_iter() + .map(|url| remote_image::dsl::link.eq::(url.into())) + .collect::>(); + insert_into(remote_image::table) + .values(forms) + .on_conflict_do_nothing() + .execute(conn) .await } @@ -100,10 +84,9 @@ impl RemoteImage { } impl ImageDetails { - pub(crate) async fn create( - conn: &mut AsyncPgConnection, - form: &ImageDetailsForm, - ) -> Result { + pub async fn create(pool: &mut DbPool<'_>, form: &ImageDetailsForm) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(image_details::table) .values(form) .on_conflict_do_nothing() diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 94bf909a3..6c72b5e18 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -51,10 +51,10 @@ impl Instance { Some(i) => Ok(i), None => { // Instance not in database yet, insert it - let form = InstanceForm::builder() - .domain(domain_) - .updated(Some(naive_now())) - .build(); + let form = InstanceForm { + updated: Some(naive_now()), + ..InstanceForm::new(domain_) + }; insert_into(instance::table) .values(&form) // Necessary because this method may be called concurrently for the same domain. This @@ -67,6 +67,11 @@ impl Instance { } } } + pub async fn read(pool: &mut DbPool<'_>, instance_id: InstanceId) -> Result { + let conn = &mut get_conn(pool).await?; + instance::table.find(instance_id).first(conn).await + } + pub async fn update( pool: &mut DbPool<'_>, instance_id: InstanceId, diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 15fcd1443..1eb6e8f04 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -9,26 +9,29 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl InstanceBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_instance_id: InstanceId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( instance_block::table.find((for_person_id, for_instance_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::InstanceIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs index 6a7b4e9ac..57420fcd4 100644 --- a/crates/db_schema/src/impls/language.rs +++ b/crates/db_schema/src/impls/language.rs @@ -1,3 +1,4 @@ +use super::actor_language::UNDETERMINED_ID; use crate::{ diesel::ExpressionMethods, newtypes::LanguageId, @@ -19,47 +20,42 @@ impl Language { language::table.find(id_).first(conn).await } - /// Attempts to find the given language code and return its ID. If not found, returns none. - pub async fn read_id_from_code( - pool: &mut DbPool<'_>, - code_: Option<&str>, - ) -> Result, Error> { - if let Some(code_) = code_ { - let conn = &mut get_conn(pool).await?; - Ok( - language::table - .filter(language::code.eq(code_)) - .first::(conn) - .await - .map(|l| l.id) - .ok(), - ) - } else { - Ok(None) - } + /// Attempts to find the given language code and return its ID. + pub async fn read_id_from_code(pool: &mut DbPool<'_>, code_: &str) -> Result { + let conn = &mut get_conn(pool).await?; + let res = language::table + .filter(language::code.eq(code_)) + .first::(conn) + .await + .map(|l| l.id); + + // Return undetermined by default + Ok(res.unwrap_or(UNDETERMINED_ID)) } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{source::language::Language, utils::build_db_pool_for_tests}; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_languages() { + async fn test_languages() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let all = Language::read_all(pool).await.unwrap(); + let all = Language::read_all(pool).await?; assert_eq!(184, all.len()); assert_eq!("ak", all[5].code); assert_eq!("lv", all[99].code); assert_eq!("yi", all[179].code); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index acff6af2a..235f053c1 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -35,9 +35,11 @@ impl LocalUser { ) -> Result { let conn = &mut get_conn(pool).await?; let mut form_with_encrypted_password = form.clone(); - let password_hash = - hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password"); - form_with_encrypted_password.password_encrypted = password_hash; + + if let Some(password_encrypted) = &form.password_encrypted { + let password_hash = hash(password_encrypted, DEFAULT_COST).expect("Couldn't hash password"); + form_with_encrypted_password.password_encrypted = Some(password_hash); + } let local_user_ = insert_into(local_user::table) .values(form_with_encrypted_password) @@ -47,9 +49,7 @@ impl LocalUser { LocalUserLanguage::update(pool, languages, local_user_.id).await?; // Create their vote_display_modes - let vote_display_mode_form = LocalUserVoteDisplayModeInsertForm::builder() - .local_user_id(local_user_.id) - .build(); + let vote_display_mode_form = LocalUserVoteDisplayModeInsertForm::new(local_user_.id); LocalUserVoteDisplayMode::create(pool, &vote_display_mode_form).await?; Ok(local_user_) @@ -136,14 +136,16 @@ impl LocalUser { diesel::delete(persons).execute(conn).await } - pub async fn is_email_taken(pool: &mut DbPool<'_>, email: &str) -> Result { + pub async fn check_is_email_taken(pool: &mut DbPool<'_>, email: &str) -> LemmyResult<()> { use diesel::dsl::{exists, select}; let conn = &mut get_conn(pool).await?; - select(exists(local_user::table.filter( + select(not(exists(local_user::table.filter( lower(coalesce(local_user::email, "")).eq(email.to_lowercase()), - ))) - .get_result(conn) - .await + )))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::EmailAlreadyExists.into()) } // TODO: maybe move this and pass in LocalUserView @@ -346,7 +348,7 @@ impl LocalUserOptionHelper for Option<&LocalUser> { impl LocalUserInsertForm { pub fn test_form(person_id: PersonId) -> Self { - Self::new(person_id, String::new()) + Self::new(person_id, Some(String::new())) } pub fn test_form_admin(person_id: PersonId) -> Self { @@ -367,7 +369,6 @@ pub struct UserBackupLists { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ source::{ @@ -419,4 +420,32 @@ mod tests { Ok(()) } + + #[tokio::test] + #[serial] + async fn test_email_taken() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let darwin_email = "charles.darwin@gmail.com"; + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + + let darwin_person = PersonInsertForm::test_form(inserted_instance.id, "darwin"); + let inserted_darwin_person = Person::create(pool, &darwin_person).await?; + + let mut darwin_local_user_form = + LocalUserInsertForm::test_form_admin(inserted_darwin_person.id); + darwin_local_user_form.email = Some(darwin_email.into()); + let _inserted_darwin_local_user = + LocalUser::create(pool, &darwin_local_user_form, vec![]).await?; + + let check = LocalUser::check_is_email_taken(pool, darwin_email).await; + assert!(check.is_err()); + + let passed_check = LocalUser::check_is_email_taken(pool, "not_charles@gmail.com").await; + assert!(passed_check.is_ok()); + + Ok(()) + } } diff --git a/crates/db_schema/src/impls/login_token.rs b/crates/db_schema/src/impls/login_token.rs index 71cac6a19..c8c44c506 100644 --- a/crates/db_schema/src/impls/login_token.rs +++ b/crates/db_schema/src/impls/login_token.rs @@ -7,6 +7,7 @@ use crate::{ }; use diesel::{delete, dsl::exists, insert_into, result::Error, select}; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl LoginToken { pub async fn create(pool: &mut DbPool<'_>, form: LoginTokenCreateForm) -> Result { @@ -22,13 +23,15 @@ impl LoginToken { pool: &mut DbPool<'_>, user_id_: LocalUserId, token_: &str, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; select(exists( login_token.find(token_).filter(user_id.eq(user_id_)), )) - .get_result(conn) - .await + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotLoggedIn.into()) } pub async fn list( diff --git a/crates/db_schema/src/impls/mod.rs b/crates/db_schema/src/impls/mod.rs index 3a4e71307..f115a101f 100644 --- a/crates/db_schema/src/impls/mod.rs +++ b/crates/db_schema/src/impls/mod.rs @@ -22,6 +22,8 @@ pub mod local_user; pub mod local_user_vote_display_mode; pub mod login_token; pub mod moderator; +pub mod oauth_account; +pub mod oauth_provider; pub mod password_reset_request; pub mod person; pub mod person_block; diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index c10d818f8..b2ef26e69 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -66,6 +66,20 @@ impl Crud for ModRemovePost { } } +impl ModRemovePost { + pub async fn create_multiple( + pool: &mut DbPool<'_>, + forms: &Vec, + ) -> Result { + use crate::schema::mod_remove_post::dsl::mod_remove_post; + let conn = &mut get_conn(pool).await?; + insert_into(mod_remove_post) + .values(forms) + .execute(conn) + .await + } +} + #[async_trait] impl Crud for ModLockPost { type InsertForm = ModLockPostForm; @@ -153,6 +167,20 @@ impl Crud for ModRemoveComment { } } +impl ModRemoveComment { + pub async fn create_multiple( + pool: &mut DbPool<'_>, + forms: &Vec, + ) -> Result { + use crate::schema::mod_remove_comment::dsl::mod_remove_comment; + let conn = &mut get_conn(pool).await?; + insert_into(mod_remove_comment) + .values(forms) + .execute(conn) + .await + } +} + #[async_trait] impl Crud for ModRemoveCommunity { type InsertForm = ModRemoveCommunityForm; @@ -465,8 +493,6 @@ impl Crud for AdminPurgeComment { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -500,51 +526,48 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> Result<(), Error> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_mod = PersonInsertForm::test_form(inserted_instance.id, "the mod"); - let inserted_mod = Person::create(pool, &new_mod).await.unwrap(); + let inserted_mod = Person::create(pool, &new_mod).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim2"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; - let new_community = CommunityInsertForm::builder() - .name("mod_community".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "mod_community".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); - 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 thweep".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); + let new_post = PostInsertForm::new( + "A test post thweep".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; - 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_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; // Now the actual tests @@ -555,13 +578,8 @@ mod tests { reason: None, removed: None, }; - let inserted_mod_remove_post = ModRemovePost::create(pool, &mod_remove_post_form) - .await - .unwrap(); - let read_mod_remove_post = ModRemovePost::read(pool, inserted_mod_remove_post.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_remove_post = ModRemovePost::create(pool, &mod_remove_post_form).await?; + let read_mod_remove_post = ModRemovePost::read(pool, inserted_mod_remove_post.id).await?; let expected_mod_remove_post = ModRemovePost { id: inserted_mod_remove_post.id, post_id: inserted_post.id, @@ -578,13 +596,8 @@ mod tests { post_id: inserted_post.id, locked: None, }; - let inserted_mod_lock_post = ModLockPost::create(pool, &mod_lock_post_form) - .await - .unwrap(); - let read_mod_lock_post = ModLockPost::read(pool, inserted_mod_lock_post.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_lock_post = ModLockPost::create(pool, &mod_lock_post_form).await?; + let read_mod_lock_post = ModLockPost::read(pool, inserted_mod_lock_post.id).await?; let expected_mod_lock_post = ModLockPost { id: inserted_mod_lock_post.id, post_id: inserted_post.id, @@ -601,13 +614,8 @@ mod tests { featured: false, is_featured_community: true, }; - let inserted_mod_feature_post = ModFeaturePost::create(pool, &mod_feature_post_form) - .await - .unwrap(); - let read_mod_feature_post = ModFeaturePost::read(pool, inserted_mod_feature_post.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_feature_post = ModFeaturePost::create(pool, &mod_feature_post_form).await?; + let read_mod_feature_post = ModFeaturePost::read(pool, inserted_mod_feature_post.id).await?; let expected_mod_feature_post = ModFeaturePost { id: inserted_mod_feature_post.id, post_id: inserted_post.id, @@ -625,13 +633,10 @@ mod tests { reason: None, removed: None, }; - let inserted_mod_remove_comment = ModRemoveComment::create(pool, &mod_remove_comment_form) - .await - .unwrap(); - let read_mod_remove_comment = ModRemoveComment::read(pool, inserted_mod_remove_comment.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_remove_comment = + ModRemoveComment::create(pool, &mod_remove_comment_form).await?; + let read_mod_remove_comment = + ModRemoveComment::read(pool, inserted_mod_remove_comment.id).await?; let expected_mod_remove_comment = ModRemoveComment { id: inserted_mod_remove_comment.id, comment_id: inserted_comment.id, @@ -650,14 +655,9 @@ mod tests { removed: None, }; let inserted_mod_remove_community = - ModRemoveCommunity::create(pool, &mod_remove_community_form) - .await - .unwrap(); + ModRemoveCommunity::create(pool, &mod_remove_community_form).await?; let read_mod_remove_community = - ModRemoveCommunity::read(pool, inserted_mod_remove_community.id) - .await - .unwrap() - .unwrap(); + ModRemoveCommunity::read(pool, inserted_mod_remove_community.id).await?; let expected_mod_remove_community = ModRemoveCommunity { id: inserted_mod_remove_community.id, community_id: inserted_community.id, @@ -678,14 +678,9 @@ mod tests { expires: None, }; let inserted_mod_ban_from_community = - ModBanFromCommunity::create(pool, &mod_ban_from_community_form) - .await - .unwrap(); + ModBanFromCommunity::create(pool, &mod_ban_from_community_form).await?; let read_mod_ban_from_community = - ModBanFromCommunity::read(pool, inserted_mod_ban_from_community.id) - .await - .unwrap() - .unwrap(); + ModBanFromCommunity::read(pool, inserted_mod_ban_from_community.id).await?; let expected_mod_ban_from_community = ModBanFromCommunity { id: inserted_mod_ban_from_community.id, community_id: inserted_community.id, @@ -706,11 +701,8 @@ mod tests { banned: None, expires: None, }; - let inserted_mod_ban = ModBan::create(pool, &mod_ban_form).await.unwrap(); - let read_mod_ban = ModBan::read(pool, inserted_mod_ban.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_ban = ModBan::create(pool, &mod_ban_form).await?; + let read_mod_ban = ModBan::read(pool, inserted_mod_ban.id).await?; let expected_mod_ban = ModBan { id: inserted_mod_ban.id, mod_person_id: inserted_mod.id, @@ -729,13 +721,8 @@ mod tests { community_id: inserted_community.id, removed: None, }; - let inserted_mod_add_community = ModAddCommunity::create(pool, &mod_add_community_form) - .await - .unwrap(); - let read_mod_add_community = ModAddCommunity::read(pool, inserted_mod_add_community.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_add_community = ModAddCommunity::create(pool, &mod_add_community_form).await?; + let read_mod_add_community = ModAddCommunity::read(pool, inserted_mod_add_community.id).await?; let expected_mod_add_community = ModAddCommunity { id: inserted_mod_add_community.id, community_id: inserted_community.id, @@ -752,11 +739,8 @@ mod tests { other_person_id: inserted_person.id, removed: None, }; - let inserted_mod_add = ModAdd::create(pool, &mod_add_form).await.unwrap(); - let read_mod_add = ModAdd::read(pool, inserted_mod_add.id) - .await - .unwrap() - .unwrap(); + let inserted_mod_add = ModAdd::create(pool, &mod_add_form).await?; + let read_mod_add = ModAdd::read(pool, inserted_mod_add.id).await?; let expected_mod_add = ModAdd { id: inserted_mod_add.id, mod_person_id: inserted_mod.id, @@ -765,14 +749,12 @@ mod tests { when_: inserted_mod_add.when_, }; - 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_mod.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + 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_mod.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(expected_mod_remove_post, read_mod_remove_post); assert_eq!(expected_mod_lock_post, read_mod_lock_post); @@ -783,5 +765,7 @@ mod tests { assert_eq!(expected_mod_ban, read_mod_ban); assert_eq!(expected_mod_add_community, read_mod_add_community); assert_eq!(expected_mod_add, read_mod_add); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/oauth_account.rs b/crates/db_schema/src/impls/oauth_account.rs new file mode 100644 index 000000000..7210b7a37 --- /dev/null +++ b/crates/db_schema/src/impls/oauth_account.rs @@ -0,0 +1,29 @@ +use crate::{ + newtypes::LocalUserId, + schema::{oauth_account, oauth_account::dsl::local_user_id}, + source::oauth_account::{OAuthAccount, OAuthAccountInsertForm}, + utils::{get_conn, DbPool}, +}; +use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; +use diesel_async::RunQueryDsl; + +impl OAuthAccount { + pub async fn create(pool: &mut DbPool<'_>, form: &OAuthAccountInsertForm) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(oauth_account::table) + .values(form) + .get_result::(conn) + .await + } + + pub async fn delete_user_accounts( + pool: &mut DbPool<'_>, + for_local_user_id: LocalUserId, + ) -> Result { + let conn = &mut get_conn(pool).await?; + + diesel::delete(oauth_account::table.filter(local_user_id.eq(for_local_user_id))) + .execute(conn) + .await + } +} diff --git a/crates/db_schema/src/impls/oauth_provider.rs b/crates/db_schema/src/impls/oauth_provider.rs new file mode 100644 index 000000000..9d7e791e7 --- /dev/null +++ b/crates/db_schema/src/impls/oauth_provider.rs @@ -0,0 +1,71 @@ +use crate::{ + newtypes::OAuthProviderId, + schema::oauth_provider, + source::oauth_provider::{ + OAuthProvider, + OAuthProviderInsertForm, + OAuthProviderUpdateForm, + PublicOAuthProvider, + }, + traits::Crud, + utils::{get_conn, DbPool}, +}; +use diesel::{dsl::insert_into, result::Error, QueryDsl}; +use diesel_async::RunQueryDsl; + +#[async_trait] +impl Crud for OAuthProvider { + type InsertForm = OAuthProviderInsertForm; + type UpdateForm = OAuthProviderUpdateForm; + type IdType = OAuthProviderId; + + async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(oauth_provider::table) + .values(form) + .get_result::(conn) + .await + } + + async fn update( + pool: &mut DbPool<'_>, + oauth_provider_id: OAuthProviderId, + form: &Self::UpdateForm, + ) -> Result { + let conn = &mut get_conn(pool).await?; + diesel::update(oauth_provider::table.find(oauth_provider_id)) + .set(form) + .get_result::(conn) + .await + } +} + +impl OAuthProvider { + pub async fn get_all(pool: &mut DbPool<'_>) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let oauth_providers = oauth_provider::table + .order(oauth_provider::id) + .select(oauth_provider::all_columns) + .load::(conn) + .await?; + + Ok(oauth_providers) + } + + pub fn convert_providers_to_public( + oauth_providers: Vec, + ) -> Vec { + let mut result = Vec::::new(); + for oauth_provider in &oauth_providers { + if oauth_provider.enabled { + result.push(PublicOAuthProvider(oauth_provider.clone())); + } + } + result + } + + pub async fn get_all_public(pool: &mut DbPool<'_>) -> Result, Error> { + let oauth_providers = OAuthProvider::get_all(pool).await?; + Ok(Self::convert_providers_to_public(oauth_providers)) + } +} diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index be05ed8ac..015db5581 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -1,5 +1,4 @@ use crate::{ - diesel::OptionalExtension, newtypes::LocalUserId, schema::password_reset_request::dsl::{password_reset_request, published, token}, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, @@ -32,20 +31,17 @@ impl PasswordResetRequest { .await } - pub async fn read_and_delete(pool: &mut DbPool<'_>, token_: &str) -> Result, Error> { + pub async fn read_and_delete(pool: &mut DbPool<'_>, token_: &str) -> Result { let conn = &mut get_conn(pool).await?; delete(password_reset_request) .filter(token.eq(token_)) .filter(published.gt(now.into_sql::() - 1.days())) .get_result(conn) .await - .optional() } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -81,9 +77,7 @@ mod tests { PasswordResetRequest::create(pool, inserted_local_user.id, token.to_string()).await?; // Read it and verify - let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token) - .await? - .unwrap(); + let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token).await?; assert_eq!( inserted_password_reset_request.id, read_password_reset_request.id @@ -102,8 +96,8 @@ mod tests { ); // Cannot reuse same token again - let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token).await?; - assert!(read_password_reset_request.is_none()); + let read_password_reset_request = PasswordResetRequest::read_and_delete(pool, token).await; + assert!(read_password_reset_request.is_err()); // Cleanup let num_deleted = Person::delete(pool, inserted_person.id).await?; diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index f2909218c..a5f8ae1a0 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -21,6 +21,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[async_trait] impl Crud for Person { @@ -29,14 +30,13 @@ impl Crud for Person { type IdType = PersonId; // Override this, so that you don't get back deleted - async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { + async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { let conn = &mut get_conn(pool).await?; person::table .filter(person::deleted.eq(false)) .find(person_id) .first(conn) .await - .optional() } async fn create(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { @@ -121,6 +121,20 @@ impl Person { .load::(conn) .await } + + pub async fn check_username_taken(pool: &mut DbPool<'_>, username: &str) -> LemmyResult<()> { + use diesel::dsl::{exists, select}; + let conn = &mut get_conn(pool).await?; + select(not(exists( + person::table + .filter(lower(person::name).eq(username.to_lowercase())) + .filter(person::local.eq(true)), + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::UsernameAlreadyExists.into()) + } } impl PersonInsertForm { @@ -221,7 +235,6 @@ impl PersonFollower { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -232,7 +245,7 @@ mod tests { traits::{Crud, Followable}, utils::build_db_pool_for_tests, }; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -266,15 +279,12 @@ mod tests { public_key: "pubkey".to_owned(), last_refreshed_at: inserted_person.published, inbox_url: inserted_person.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: inserted_instance.id, }; - let read_person = Person::read(pool, inserted_person.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + let read_person = Person::read(pool, inserted_person.id).await?; let update_person_form = PersonUpdateForm { actor_id: Some(inserted_person.actor_id.clone()), diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index cfd41f6d6..7f2286616 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -9,7 +9,7 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, @@ -17,19 +17,22 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl PersonBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_recipient_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( person_block::table.find((for_person_id, for_recipient_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 8e14bee9f..fb6245585 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -1,7 +1,7 @@ use crate::{ - diesel::OptionalExtension, + diesel::{BoolExpressionMethods, OptionalExtension}, newtypes::{CommunityId, DbUrl, PersonId, PostId}, - schema::{post, post_hide, post_like, post_read, post_saved}, + schema::{community, person, post, post_hide, post_like, post_read, post_saved}, source::post::{ Post, PostHide, @@ -20,6 +20,7 @@ use crate::{ functions::coalesce, get_conn, naive_now, + now, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -30,7 +31,7 @@ use crate::{ use ::url::Url; use chrono::{DateTime, Utc}; use diesel::{ - dsl::insert_into, + dsl::{count, insert_into, not}, result::Error, DecoratableTarget, ExpressionMethods, @@ -68,6 +69,10 @@ impl Crud for Post { } impl Post { + pub async fn read_xx(pool: &mut DbPool<'_>, id: PostId) -> Result { + let conn = &mut *get_conn(pool).await?; + post::table.find(id).first(conn).await + } pub async fn insert_apub( pool: &mut DbPool<'_>, timestamp: DateTime, @@ -140,7 +145,7 @@ impl Post { pool: &mut DbPool<'_>, for_creator_id: PersonId, for_community_id: Option, - new_removed: bool, + removed: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; @@ -152,7 +157,7 @@ impl Post { } update - .set((post::removed.eq(new_removed), post::updated.eq(naive_now()))) + .set((post::removed.eq(removed), post::updated.eq(naive_now()))) .get_results::(conn) .await } @@ -169,6 +174,7 @@ impl Post { let object_id: DbUrl = object_id.into(); post::table .filter(post::ap_id.eq(object_id)) + .filter(post::scheduled_publish_time.is_null()) .first(conn) .await .optional() @@ -242,6 +248,28 @@ impl Post { .get_results::(conn) .await } + + pub async fn user_scheduled_post_count( + person_id: PersonId, + pool: &mut DbPool<'_>, + ) -> Result { + let conn = &mut get_conn(pool).await?; + + post::table + .inner_join(person::table) + .inner_join(community::table) + // find all posts which have scheduled_publish_time that is in the future + .filter(post::scheduled_publish_time.is_not_null()) + .filter(coalesce(post::scheduled_publish_time, now()).gt(now())) + // make sure the post and community are still around + .filter(not(post::deleted.or(post::removed))) + .filter(not(community::removed.or(community::deleted))) + // only posts by specified user + .filter(post::creator_id.eq(person_id)) + .select(count(post::id)) + .first::(conn) + .await + } } #[async_trait] @@ -364,8 +392,6 @@ impl PostHide { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -387,6 +413,8 @@ mod tests { traits::{Crud, Likeable, Saveable}, utils::build_db_pool_for_tests, }; + use chrono::DateTime; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; use std::collections::HashSet; @@ -394,41 +422,44 @@ mod tests { #[tokio::test] #[serial] - async fn test_crud() { + 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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; - let new_community = CommunityInsertForm::builder() - .name("test community_3".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community_3".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); - 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".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); + let new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let new_post2 = PostInsertForm::new( + "A test post 2".into(), + inserted_person.id, + inserted_community.id, + ); + let inserted_post2 = Post::create(pool, &new_post2).await?; - let new_post2 = PostInsertForm::builder() - .name("A test post 2".into()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .build(); - let inserted_post2 = Post::create(pool, &new_post2).await.unwrap(); + let new_scheduled_post = PostInsertForm { + scheduled_publish_time: Some(DateTime::from_timestamp_nanos(i64::MAX)), + ..PostInsertForm::new("beans".into(), inserted_person.id, inserted_community.id) + }; + let inserted_scheduled_post = Post::create(pool, &new_scheduled_post).await?; let expected_post = Post { id: inserted_post.id, @@ -448,14 +479,13 @@ mod tests { embed_description: None, embed_video_url: None, thumbnail_url: None, - ap_id: Url::parse(&format!("https://lemmy-alpha/post/{}", inserted_post.id)) - .unwrap() - .into(), + ap_id: Url::parse(&format!("https://lemmy-alpha/post/{}", inserted_post.id))?.into(), local: true, language_id: Default::default(), featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }; // Post Like @@ -465,7 +495,7 @@ mod tests { score: 1, }; - let inserted_post_like = PostLike::like(pool, &post_like_form).await.unwrap(); + let inserted_post_like = PostLike::like(pool, &post_like_form).await?; let expected_post_like = PostLike { post_id: inserted_post.id, @@ -480,7 +510,7 @@ mod tests { person_id: inserted_person.id, }; - let inserted_post_saved = PostSaved::save(pool, &post_saved_form).await.unwrap(); + let inserted_post_saved = PostSaved::save(pool, &post_saved_form).await?; let expected_post_saved = PostSaved { post_id: inserted_post.id, @@ -494,48 +524,47 @@ mod tests { HashSet::from([inserted_post.id, inserted_post2.id]), inserted_person.id, ) - .await - .unwrap(); + .await?; assert_eq!(2, marked_as_read); - let read_post = Post::read(pool, inserted_post.id).await.unwrap().unwrap(); + let read_post = Post::read(pool, inserted_post.id).await?; let new_post_update = PostUpdateForm { name: Some("A test post".into()), ..Default::default() }; - let updated_post = Post::update(pool, inserted_post.id, &new_post_update) - .await - .unwrap(); + let updated_post = Post::update(pool, inserted_post.id, &new_post_update).await?; - let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) - .await - .unwrap(); + // Scheduled post count + let scheduled_post_count = Post::user_scheduled_post_count(inserted_person.id, pool).await?; + assert_eq!(1, scheduled_post_count); + + let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id).await?; assert_eq!(1, like_removed); - let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); + let saved_removed = PostSaved::unsave(pool, &post_saved_form).await?; assert_eq!(1, saved_removed); let read_removed = PostRead::mark_as_unread( pool, HashSet::from([inserted_post.id, inserted_post2.id]), inserted_person.id, ) - .await - .unwrap(); + .await?; assert_eq!(2, read_removed); - let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() - + Post::delete(pool, inserted_post2.id).await.unwrap(); - assert_eq!(2, num_deleted); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + let num_deleted = Post::delete(pool, inserted_post.id).await? + + Post::delete(pool, inserted_post2.id).await? + + Post::delete(pool, inserted_scheduled_post.id).await?; + assert_eq!(3, num_deleted); + Community::delete(pool, inserted_community.id).await?; + Person::delete(pool, inserted_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(expected_post, read_post); assert_eq!(expected_post, inserted_post); assert_eq!(expected_post, updated_post); assert_eq!(expected_post_like, inserted_post_like); assert_eq!(expected_post_saved, inserted_post_saved); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/post_report.rs b/crates/db_schema/src/impls/post_report.rs index 7218ef468..5507423e1 100644 --- a/crates/db_schema/src/impls/post_report.rs +++ b/crates/db_schema/src/impls/post_report.rs @@ -80,8 +80,6 @@ impl Reportable for PostReport { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; @@ -95,29 +93,24 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use diesel::result::Error; use serial_test::serial; - async fn init(pool: &mut DbPool<'_>) -> (Person, PostReport) { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + async fn init(pool: &mut DbPool<'_>) -> Result<(Person, PostReport), Error> { + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let person_form = PersonInsertForm::test_form(inserted_instance.id, "jim"); - let person = Person::create(pool, &person_form).await.unwrap(); + let person = Person::create(pool, &person_form).await?; - let community_form = CommunityInsertForm::builder() - .name("test community_4".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - let community = Community::create(pool, &community_form).await.unwrap(); + let community_form = CommunityInsertForm::new( + inserted_instance.id, + "test community_4".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; - let form = PostInsertForm::builder() - .name("A test post".into()) - .creator_id(person.id) - .community_id(community.id) - .build(); - let post = Post::create(pool, &form).await.unwrap(); + let form = PostInsertForm::new("A test post".into(), person.id, community.id); + let post = Post::create(pool, &form).await?; let report_form = PostReportForm { post_id: post.id, @@ -125,46 +118,46 @@ mod tests { reason: "my reason".to_string(), ..Default::default() }; - let report = PostReport::report(pool, &report_form).await.unwrap(); - (person, report) + let report = PostReport::report(pool, &report_form).await?; + + Ok((person, report)) } #[tokio::test] #[serial] - async fn test_resolve_post_report() { + async fn test_resolve_post_report() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (person, report) = init(pool).await; + let (person, report) = init(pool).await?; - let resolved_count = PostReport::resolve(pool, report.id, person.id) - .await - .unwrap(); + let resolved_count = PostReport::resolve(pool, report.id, person.id).await?; assert_eq!(resolved_count, 1); - let unresolved_count = PostReport::unresolve(pool, report.id, person.id) - .await - .unwrap(); + let unresolved_count = PostReport::unresolve(pool, report.id, person.id).await?; assert_eq!(unresolved_count, 1); - Person::delete(pool, person.id).await.unwrap(); - Post::delete(pool, report.post_id).await.unwrap(); + Person::delete(pool, person.id).await?; + Post::delete(pool, report.post_id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn test_resolve_all_post_reports() { + async fn test_resolve_all_post_reports() -> Result<(), Error> { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let (person, report) = init(pool).await; + let (person, report) = init(pool).await?; - let resolved_count = PostReport::resolve_all_for_object(pool, report.post_id, person.id) - .await - .unwrap(); + let resolved_count = + PostReport::resolve_all_for_object(pool, report.post_id, person.id).await?; assert_eq!(resolved_count, 1); - Person::delete(pool, person.id).await.unwrap(); - Post::delete(pool, report.post_id).await.unwrap(); + Person::delete(pool, person.id).await?; + Post::delete(pool, report.post_id).await?; + + Ok(()) } } diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index fe3629a1a..264175fe2 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -85,8 +85,6 @@ impl PrivateMessage { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -98,37 +96,34 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; use url::Url; #[tokio::test] #[serial] - async fn test_crud() { + 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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let creator_form = PersonInsertForm::test_form(inserted_instance.id, "creator_pm"); - let inserted_creator = Person::create(pool, &creator_form).await.unwrap(); + let inserted_creator = Person::create(pool, &creator_form).await?; let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "recipient_pm"); - let inserted_recipient = Person::create(pool, &recipient_form).await.unwrap(); + let inserted_recipient = Person::create(pool, &recipient_form).await?; - let private_message_form = PrivateMessageInsertForm::builder() - .content("A test private message".into()) - .creator_id(inserted_creator.id) - .recipient_id(inserted_recipient.id) - .build(); + let private_message_form = PrivateMessageInsertForm::new( + inserted_creator.id, + inserted_recipient.id, + "A test private message".into(), + ); - let inserted_private_message = PrivateMessage::create(pool, &private_message_form) - .await - .unwrap(); + let inserted_private_message = PrivateMessage::create(pool, &private_message_form).await?; let expected_private_message = PrivateMessage { id: inserted_private_message.id, @@ -142,16 +137,12 @@ mod tests { ap_id: Url::parse(&format!( "https://lemmy-alpha/private_message/{}", inserted_private_message.id - )) - .unwrap() + ))? .into(), local: true, }; - let read_private_message = PrivateMessage::read(pool, inserted_private_message.id) - .await - .unwrap() - .unwrap(); + let read_private_message = PrivateMessage::read(pool, inserted_private_message.id).await?; let private_message_update_form = PrivateMessageUpdateForm { content: Some("A test private message".into()), @@ -162,8 +153,7 @@ mod tests { inserted_private_message.id, &private_message_update_form, ) - .await - .unwrap(); + .await?; let deleted_private_message = PrivateMessage::update( pool, @@ -173,8 +163,7 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); + .await?; let marked_read_private_message = PrivateMessage::update( pool, inserted_private_message.id, @@ -183,16 +172,17 @@ mod tests { ..Default::default() }, ) - .await - .unwrap(); - Person::delete(pool, inserted_creator.id).await.unwrap(); - Person::delete(pool, inserted_recipient.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + .await?; + Person::delete(pool, inserted_creator.id).await?; + Person::delete(pool, inserted_recipient.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(expected_private_message, read_private_message); assert_eq!(expected_private_message, updated_private_message); assert_eq!(expected_private_message, inserted_private_message); assert!(deleted_private_message.deleted); assert!(marked_read_private_message.read); + + Ok(()) } } diff --git a/crates/db_schema/src/impls/registration_application.rs b/crates/db_schema/src/impls/registration_application.rs index 055ffb51f..d9777919d 100644 --- a/crates/db_schema/src/impls/registration_application.rs +++ b/crates/db_schema/src/impls/registration_application.rs @@ -1,5 +1,4 @@ use crate::{ - diesel::OptionalExtension, newtypes::{LocalUserId, RegistrationApplicationId}, schema::registration_application::dsl::{local_user_id, registration_application}, source::registration_application::{ @@ -44,12 +43,11 @@ impl RegistrationApplication { pub async fn find_by_local_user_id( pool: &mut DbPool<'_>, local_user_id_: LocalUserId, - ) -> Result, Error> { + ) -> Result { let conn = &mut get_conn(pool).await?; registration_application .filter(local_user_id.eq(local_user_id_)) .first(conn) .await - .optional() } } diff --git a/crates/db_schema/src/impls/secret.rs b/crates/db_schema/src/impls/secret.rs index 1365ea838..bfff270b6 100644 --- a/crates/db_schema/src/impls/secret.rs +++ b/crates/db_schema/src/impls/secret.rs @@ -1,5 +1,4 @@ use crate::{ - diesel::OptionalExtension, schema::secret::dsl::secret, source::secret::Secret, utils::{get_conn, DbPool}, @@ -10,12 +9,12 @@ use diesel_async::RunQueryDsl; impl Secret { /// Initialize the Secrets from the DB. /// Warning: You should only call this once. - pub async fn init(pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn init(pool: &mut DbPool<'_>) -> Result { Self::read_secrets(pool).await } - async fn read_secrets(pool: &mut DbPool<'_>) -> Result, Error> { + async fn read_secrets(pool: &mut DbPool<'_>) -> Result { let conn = &mut get_conn(pool).await?; - secret.first(conn).await.optional() + secret.first(conn).await } } diff --git a/crates/db_schema/src/impls/site.rs b/crates/db_schema/src/impls/site.rs index 9dbd2401d..8f57647a3 100644 --- a/crates/db_schema/src/impls/site.rs +++ b/crates/db_schema/src/impls/site.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{DbUrl, InstanceId, SiteId}, - schema::site, + schema::{local_site, site}, source::{ actor_language::SiteLanguage, site::{Site, SiteInsertForm, SiteUpdateForm}, @@ -10,6 +10,7 @@ use crate::{ }; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl}; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use url::Url; #[async_trait] @@ -19,7 +20,7 @@ impl Crud for Site { type IdType = SiteId; /// Use SiteView::read_local, or Site::read_from_apub_id instead - async fn read(_pool: &mut DbPool<'_>, _site_id: SiteId) -> Result, Error> { + async fn read(_pool: &mut DbPool<'_>, _site_id: SiteId) -> Result { Err(Error::NotFound) } @@ -102,4 +103,18 @@ impl Site { url.set_query(None); url } + + pub async fn read_local(pool: &mut DbPool<'_>) -> LemmyResult { + let conn = &mut get_conn(pool).await?; + + Ok( + site::table + .inner_join(local_site::table) + .select(site::all_columns) + .first(conn) + .await + .optional()? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?, + ) + } } diff --git a/crates/db_schema/src/impls/tagline.rs b/crates/db_schema/src/impls/tagline.rs index be4860e17..aa5841020 100644 --- a/crates/db_schema/src/impls/tagline.rs +++ b/crates/db_schema/src/impls/tagline.rs @@ -1,58 +1,59 @@ use crate::{ - newtypes::LocalSiteId, - schema::tagline::dsl::{local_site_id, tagline}, - source::tagline::{Tagline, TaglineForm}, - utils::{get_conn, DbPool}, + newtypes::TaglineId, + schema::tagline::dsl::{published, tagline}, + source::tagline::{Tagline, TaglineInsertForm, TaglineUpdateForm}, + traits::Crud, + utils::{get_conn, limit_and_offset, DbPool}, }; use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; -use diesel_async::{AsyncPgConnection, RunQueryDsl}; +use diesel_async::RunQueryDsl; -impl Tagline { - pub async fn replace( - pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, - list_content: Option>, - ) -> Result, Error> { +#[async_trait] +impl Crud for Tagline { + type InsertForm = TaglineInsertForm; + type UpdateForm = TaglineUpdateForm; + type IdType = TaglineId; + + async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; - if let Some(list) = list_content { - conn - .build_transaction() - .run(|conn| { - Box::pin(async move { - Self::clear(conn).await?; - - for item in list { - let form = TaglineForm { - local_site_id: for_local_site_id, - content: item, - updated: None, - }; - insert_into(tagline) - .values(form) - .get_result::(conn) - .await?; - } - Self::get_all(&mut conn.into(), for_local_site_id).await - }) as _ - }) - .await - } else { - Self::get_all(&mut conn.into(), for_local_site_id).await - } + insert_into(tagline) + .values(form) + .get_result::(conn) + .await } - async fn clear(conn: &mut AsyncPgConnection) -> Result { - diesel::delete(tagline).execute(conn).await - } - - pub async fn get_all( + async fn update( pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, - ) -> Result, Error> { + tagline_id: TaglineId, + new_tagline: &Self::UpdateForm, + ) -> Result { let conn = &mut get_conn(pool).await?; - tagline - .filter(local_site_id.eq(for_local_site_id)) - .get_results::(conn) + diesel::update(tagline.find(tagline_id)) + .set(new_tagline) + .get_result::(conn) .await } } + +impl Tagline { + pub async fn list( + pool: &mut DbPool<'_>, + page: Option, + limit: Option, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + tagline + .order(published.desc()) + .offset(offset) + .limit(limit) + .get_results::(conn) + .await + } + + pub async fn get_random(pool: &mut DbPool<'_>) -> Result { + let conn = &mut get_conn(pool).await?; + sql_function!(fn random() -> Text); + tagline.order(random()).limit(1).first::(conn).await + } +} diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index c29ec6443..dbadaaf95 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -27,7 +27,6 @@ pub mod newtypes; pub mod sensitive; #[cfg(feature = "full")] #[rustfmt::skip] -#[allow(clippy::wildcard_imports)] pub mod schema; #[cfg(feature = "full")] pub mod aliases { @@ -58,13 +57,13 @@ use ts_rs::TS; #[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr( feature = "full", - ExistingTypePath = "crate::schema::sql_types::SortTypeEnum" + ExistingTypePath = "crate::schema::sql_types::PostSortTypeEnum" )] #[cfg_attr(feature = "full", DbValueStyle = "verbatim")] #[cfg_attr(feature = "full", ts(export))] // TODO add the controversial and scaled rankings to the doc below /// The post sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html -pub enum SortType { +pub enum PostSortType { #[default] Active, Hot, @@ -87,11 +86,19 @@ pub enum SortType { Scaled, } -#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash, +)] +#[cfg_attr(feature = "full", derive(DbEnum, TS))] +#[cfg_attr( + feature = "full", + ExistingTypePath = "crate::schema::sql_types::CommentSortTypeEnum" +)] +#[cfg_attr(feature = "full", DbValueStyle = "verbatim")] #[cfg_attr(feature = "full", ts(export))] /// The comment sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html pub enum CommentSortType { + #[default] Hot, Top, New, @@ -174,7 +181,6 @@ pub enum SearchType { Posts, Communities, Users, - Url, } #[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)] @@ -245,6 +251,27 @@ pub enum CommunityVisibility { LocalOnly, } +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash, +)] +#[cfg_attr(feature = "full", derive(DbEnum, TS))] +#[cfg_attr( + feature = "full", + ExistingTypePath = "crate::schema::sql_types::FederationModeEnum" +)] +#[cfg_attr(feature = "full", DbValueStyle = "verbatim")] +#[cfg_attr(feature = "full", ts(export))] +/// The federation mode for an item +pub enum FederationMode { + #[default] + /// Allows all + All, + /// Allows only local + Local, + /// Disables + Disable, +} + /// Wrapper for assert_eq! macro. Checks that vec matches the given length, and prints the /// vec on failure. #[macro_export] diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index c715305bb..fe1febef5 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -148,12 +148,24 @@ pub struct LocalSiteId(i32); /// The custom emoji id. pub struct CustomEmojiId(i32); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "full", derive(DieselNewType, TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The tagline id. +pub struct TaglineId(i32); + #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", ts(export))] /// The registration application id. pub struct RegistrationApplicationId(i32); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "full", derive(DieselNewType, TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The oauth provider id. +pub struct OAuthProviderId(pub i32); + #[cfg(feature = "full")] #[derive(Serialize, Deserialize)] #[serde(remote = "Ltree")] @@ -179,13 +191,13 @@ impl Display for DbUrl { } // the project doesn't compile with From -#[allow(clippy::from_over_into)] +#[expect(clippy::from_over_into)] impl Into for Url { fn into(self) -> DbUrl { DbUrl(Box::new(self)) } } -#[allow(clippy::from_over_into)] +#[expect(clippy::from_over_into)] impl Into for DbUrl { fn into(self) -> Url { *self.0 diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index c78acfe37..544da607b 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -5,10 +5,18 @@ pub mod sql_types { #[diesel(postgres_type(name = "actor_type_enum"))] pub struct ActorTypeEnum; + #[derive(diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "comment_sort_type_enum"))] + pub struct CommentSortTypeEnum; + #[derive(diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "community_visibility"))] pub struct CommunityVisibility; + #[derive(diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "federation_mode_enum"))] + pub struct FederationModeEnum; + #[derive(diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "listing_type_enum"))] pub struct ListingTypeEnum; @@ -22,12 +30,12 @@ pub mod sql_types { pub struct PostListingModeEnum; #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "registration_mode_enum"))] - pub struct RegistrationModeEnum; + #[diesel(postgres_type(name = "post_sort_type_enum"))] + pub struct PostSortTypeEnum; #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "sort_type_enum"))] - pub struct SortTypeEnum; + #[diesel(postgres_type(name = "registration_mode_enum"))] + pub struct RegistrationModeEnum; } diesel::table! { @@ -115,7 +123,6 @@ diesel::table! { comment_like (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, - post_id -> Int4, score -> Int2, published -> Timestamptz, } @@ -181,8 +188,6 @@ diesel::table! { followers_url -> Nullable, #[max_length = 255] inbox_url -> Varchar, - #[max_length = 255] - shared_inbox_url -> Nullable, hidden -> Bool, posting_restricted_to_mods -> Bool, instance_id -> Int4, @@ -254,7 +259,6 @@ diesel::table! { diesel::table! { custom_emoji (id) { id -> Int4, - local_site_id -> Int4, #[max_length = 128] shortcode -> Varchar, image_url -> Text, @@ -363,14 +367,14 @@ diesel::table! { use super::sql_types::ListingTypeEnum; use super::sql_types::RegistrationModeEnum; use super::sql_types::PostListingModeEnum; - use super::sql_types::SortTypeEnum; + use super::sql_types::PostSortTypeEnum; + use super::sql_types::CommentSortTypeEnum; + use super::sql_types::FederationModeEnum; local_site (id) { id -> Int4, site_id -> Int4, site_setup -> Bool, - enable_downvotes -> Bool, - enable_nsfw -> Bool, community_creation_admin_only -> Bool, require_email_verification -> Bool, application_question -> Nullable, @@ -392,7 +396,13 @@ diesel::table! { reports_email_admins -> Bool, federation_signed_fetch -> Bool, default_post_listing_mode -> PostListingModeEnum, - default_sort_type -> SortTypeEnum, + default_post_sort_type -> PostSortTypeEnum, + default_comment_sort_type -> CommentSortTypeEnum, + oauth_registration -> Bool, + post_upvotes -> FederationModeEnum, + post_downvotes -> FederationModeEnum, + comment_upvotes -> FederationModeEnum, + comment_downvotes -> FederationModeEnum, } } @@ -429,18 +439,19 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; - use super::sql_types::SortTypeEnum; + use super::sql_types::PostSortTypeEnum; use super::sql_types::ListingTypeEnum; use super::sql_types::PostListingModeEnum; + use super::sql_types::CommentSortTypeEnum; local_user (id) { id -> Int4, person_id -> Int4, - password_encrypted -> Text, + password_encrypted -> Nullable, email -> Nullable, show_nsfw -> Bool, theme -> Text, - default_sort_type -> SortTypeEnum, + default_post_sort_type -> PostSortTypeEnum, default_listing_type -> ListingTypeEnum, #[max_length = 20] interface_language -> Varchar, @@ -453,7 +464,6 @@ diesel::table! { totp_2fa_secret -> Nullable, open_links_in_new_tab -> Bool, blur_nsfw -> Bool, - auto_expand -> Bool, infinite_scroll_enabled -> Bool, admin -> Bool, post_listing_mode -> PostListingModeEnum, @@ -461,6 +471,7 @@ diesel::table! { enable_keyboard_navigation -> Bool, enable_animated_images -> Bool, collapse_bot_comments -> Bool, + default_comment_sort_type -> CommentSortTypeEnum, } } @@ -612,6 +623,36 @@ diesel::table! { } } +diesel::table! { + oauth_account (oauth_provider_id, local_user_id) { + local_user_id -> Int4, + oauth_provider_id -> Int4, + oauth_user_id -> Text, + published -> Timestamptz, + updated -> Nullable, + } +} + +diesel::table! { + oauth_provider (id) { + id -> Int4, + display_name -> Text, + issuer -> Text, + authorization_endpoint -> Text, + token_endpoint -> Text, + userinfo_endpoint -> Text, + id_claim -> Text, + client_id -> Text, + client_secret -> Text, + scopes -> Text, + auto_verify_email -> Bool, + account_linking_enabled -> Bool, + enabled -> Bool, + published -> Timestamptz, + updated -> Nullable, + } +} + diesel::table! { password_reset_request (id) { id -> Int4, @@ -643,8 +684,6 @@ diesel::table! { deleted -> Bool, #[max_length = 255] inbox_url -> Varchar, - #[max_length = 255] - shared_inbox_url -> Nullable, matrix_user_id -> Nullable, bot_account -> Bool, ban_expires -> Nullable, @@ -733,6 +772,7 @@ diesel::table! { featured_local -> Bool, url_content_type -> Nullable, alt_text -> Nullable, + scheduled_publish_time -> Nullable, } } @@ -936,7 +976,6 @@ diesel::table! { diesel::table! { tagline (id) { id -> Int4, - local_site_id -> Int4, content -> Text, published -> Timestamptz, updated -> Nullable, @@ -955,7 +994,6 @@ diesel::joinable!(comment -> post (post_id)); diesel::joinable!(comment_aggregates -> comment (comment_id)); diesel::joinable!(comment_like -> comment (comment_id)); diesel::joinable!(comment_like -> person (person_id)); -diesel::joinable!(comment_like -> post (post_id)); diesel::joinable!(comment_reply -> comment (comment_id)); diesel::joinable!(comment_reply -> person (recipient_id)); diesel::joinable!(comment_report -> comment (comment_id)); @@ -973,7 +1011,6 @@ diesel::joinable!(community_moderator -> community (community_id)); diesel::joinable!(community_moderator -> person (person_id)); diesel::joinable!(community_person_ban -> community (community_id)); diesel::joinable!(community_person_ban -> person (person_id)); -diesel::joinable!(custom_emoji -> local_site (local_site_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(federation_allowlist -> instance (instance_id)); @@ -1004,6 +1041,8 @@ diesel::joinable!(mod_remove_community -> person (mod_person_id)); diesel::joinable!(mod_remove_post -> person (mod_person_id)); diesel::joinable!(mod_remove_post -> post (post_id)); diesel::joinable!(mod_transfer_community -> community (community_id)); +diesel::joinable!(oauth_account -> local_user (local_user_id)); +diesel::joinable!(oauth_account -> oauth_provider (oauth_provider_id)); diesel::joinable!(password_reset_request -> local_user (local_user_id)); diesel::joinable!(person -> instance (instance_id)); diesel::joinable!(person_aggregates -> person (person_id)); @@ -1035,7 +1074,6 @@ diesel::joinable!(site -> instance (instance_id)); diesel::joinable!(site_aggregates -> site (site_id)); diesel::joinable!(site_language -> language (language_id)); diesel::joinable!(site_language -> site (site_id)); -diesel::joinable!(tagline -> local_site (local_site_id)); diesel::allow_tables_to_appear_in_same_query!( admin_purge_comment, @@ -1085,6 +1123,8 @@ diesel::allow_tables_to_appear_in_same_query!( mod_remove_community, mod_remove_post, mod_transfer_community, + oauth_account, + oauth_provider, password_reset_request, person, person_aggregates, diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 74ae0b7f6..1e5f043f1 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -10,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)] @@ -51,24 +50,28 @@ pub struct Comment { pub language_id: LanguageId, } -#[derive(Debug, Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = comment))] pub struct CommentInsertForm { - #[builder(!default)] pub creator_id: PersonId, - #[builder(!default)] pub post_id: PostId, - #[builder(!default)] pub content: String, + #[new(default)] pub removed: Option, + #[new(default)] pub published: Option>, + #[new(default)] pub updated: Option>, + #[new(default)] pub deleted: Option, + #[new(default)] pub ap_id: Option, + #[new(default)] pub local: Option, + #[new(default)] pub distinguished: Option, + #[new(default)] pub language_id: Option, } @@ -99,7 +102,6 @@ pub struct CommentUpdateForm { pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, - pub post_id: PostId, // TODO this is redundant pub score: i16, pub published: DateTime, } @@ -110,7 +112,6 @@ pub struct CommentLike { pub struct CommentLikeForm { pub person_id: PersonId, pub comment_id: CommentId, - pub post_id: PostId, // TODO this is redundant pub score: i16, } diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index fe7f120ec..853667f7f 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -11,7 +11,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)] @@ -55,8 +54,6 @@ pub struct Community { #[cfg_attr(feature = "full", ts(skip))] #[serde(skip, default = "placeholder_apub_url")] pub inbox_url: DbUrl, - #[serde(skip)] - pub shared_inbox_url: Option, /// Whether the community is hidden. pub hidden: bool, /// Whether posting is restricted to mods only. @@ -71,37 +68,51 @@ pub struct Community { pub visibility: CommunityVisibility, } -#[derive(Debug, Clone, TypedBuilder, Default)] -#[builder(field_defaults(default))] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = community))] pub struct CommunityInsertForm { - #[builder(!default)] - pub name: String, - #[builder(!default)] - pub title: String, - pub description: Option, - pub removed: Option, - pub published: Option>, - pub updated: Option>, - pub deleted: Option, - pub nsfw: Option, - pub actor_id: Option, - pub local: Option, - pub private_key: Option, - pub public_key: String, - pub last_refreshed_at: Option>, - pub icon: Option, - pub banner: Option, - pub followers_url: Option, - pub inbox_url: Option, - pub shared_inbox_url: Option, - pub moderators_url: Option, - pub featured_url: Option, - pub hidden: Option, - pub posting_restricted_to_mods: Option, - #[builder(!default)] pub instance_id: InstanceId, + pub name: String, + pub title: String, + pub public_key: String, + #[new(default)] + pub description: Option, + #[new(default)] + pub removed: Option, + #[new(default)] + pub published: Option>, + #[new(default)] + pub updated: Option>, + #[new(default)] + pub deleted: Option, + #[new(default)] + pub nsfw: Option, + #[new(default)] + pub actor_id: Option, + #[new(default)] + pub local: Option, + #[new(default)] + pub private_key: Option, + #[new(default)] + pub last_refreshed_at: Option>, + #[new(default)] + pub icon: Option, + #[new(default)] + pub banner: Option, + #[new(default)] + pub followers_url: Option, + #[new(default)] + pub inbox_url: Option, + #[new(default)] + pub moderators_url: Option, + #[new(default)] + pub featured_url: Option, + #[new(default)] + pub hidden: Option, + #[new(default)] + pub posting_restricted_to_mods: Option, + #[new(default)] pub visibility: Option, } @@ -125,7 +136,6 @@ pub struct CommunityUpdateForm { pub banner: Option>, pub followers_url: Option, pub inbox_url: Option, - pub shared_inbox_url: Option>, pub moderators_url: Option, pub featured_url: Option, pub hidden: Option, diff --git a/crates/db_schema/src/source/custom_emoji.rs b/crates/db_schema/src/source/custom_emoji.rs index 3217c9736..f5a92ea46 100644 --- a/crates/db_schema/src/source/custom_emoji.rs +++ b/crates/db_schema/src/source/custom_emoji.rs @@ -1,4 +1,4 @@ -use crate::newtypes::{CustomEmojiId, DbUrl, LocalSiteId}; +use crate::newtypes::{CustomEmojiId, DbUrl}; #[cfg(feature = "full")] use crate::schema::custom_emoji; use chrono::{DateTime, Utc}; @@ -6,25 +6,16 @@ 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(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable, TS) -)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] -#[cfg_attr( - feature = "full", - diesel(belongs_to(crate::source::local_site::LocalSite)) -)] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A custom emoji. pub struct CustomEmoji { pub id: CustomEmojiId, - pub local_site_id: LocalSiteId, pub shortcode: String, pub image_url: DbUrl, pub alt_text: String, @@ -33,22 +24,20 @@ pub struct CustomEmoji { pub updated: Option>, } -#[derive(Debug, Clone, TypedBuilder)] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] pub struct CustomEmojiInsertForm { - pub local_site_id: LocalSiteId, pub shortcode: String, pub image_url: DbUrl, pub alt_text: String, pub category: String, } -#[derive(Debug, Clone, TypedBuilder)] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] pub struct CustomEmojiUpdateForm { - pub local_site_id: LocalSiteId, pub image_url: DbUrl, pub alt_text: String, pub category: String, diff --git a/crates/db_schema/src/source/custom_emoji_keyword.rs b/crates/db_schema/src/source/custom_emoji_keyword.rs index 34ee071b5..a47ba411e 100644 --- a/crates/db_schema/src/source/custom_emoji_keyword.rs +++ b/crates/db_schema/src/source/custom_emoji_keyword.rs @@ -4,7 +4,6 @@ use crate::schema::custom_emoji_keyword; use serde::{Deserialize, Serialize}; #[cfg(feature = "full")] use ts_rs::TS; -use typed_builder::TypedBuilder; #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[cfg_attr( @@ -25,7 +24,7 @@ pub struct CustomEmojiKeyword { pub keyword: String, } -#[derive(Debug, Clone, TypedBuilder)] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji_keyword))] pub struct CustomEmojiKeywordInsertForm { diff --git a/crates/db_schema/src/source/image_upload.rs b/crates/db_schema/src/source/image_upload.rs index b72c55065..db840dc1d 100644 --- a/crates/db_schema/src/source/image_upload.rs +++ b/crates/db_schema/src/source/image_upload.rs @@ -7,7 +7,6 @@ use serde_with::skip_serializing_none; use std::fmt::Debug; #[cfg(feature = "full")] use ts_rs::TS; -use typed_builder::TypedBuilder; #[skip_serializing_none] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] @@ -30,7 +29,7 @@ pub struct ImageUpload { pub published: DateTime, } -#[derive(Debug, Clone, TypedBuilder)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = image_upload))] pub struct ImageUploadForm { diff --git a/crates/db_schema/src/source/images.rs b/crates/db_schema/src/source/images.rs index 0dea4b84f..22f5e6eb4 100644 --- a/crates/db_schema/src/source/images.rs +++ b/crates/db_schema/src/source/images.rs @@ -51,13 +51,6 @@ pub struct RemoteImage { pub published: DateTime, } -#[derive(Debug, Clone)] -#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = remote_image))] -pub struct RemoteImageForm { - pub link: DbUrl, -} - #[skip_serializing_none] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] diff --git a/crates/db_schema/src/source/instance.rs b/crates/db_schema/src/source/instance.rs index 98e0d401b..8c27a2cb6 100644 --- a/crates/db_schema/src/source/instance.rs +++ b/crates/db_schema/src/source/instance.rs @@ -7,7 +7,6 @@ use serde_with::skip_serializing_none; use std::fmt::Debug; #[cfg(feature = "full")] use ts_rs::TS; -use typed_builder::TypedBuilder; #[skip_serializing_none] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -25,14 +24,15 @@ pub struct Instance { pub version: Option, } -#[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 = instance))] pub struct InstanceForm { - #[builder(!default)] pub domain: String, + #[new(default)] pub software: Option, + #[new(default)] pub version: Option, + #[new(default)] pub updated: Option>, } diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index 05583c065..5fa57fe3b 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -2,17 +2,18 @@ use crate::schema::local_site; use crate::{ newtypes::{LocalSiteId, SiteId}, + CommentSortType, + FederationMode, ListingType, PostListingMode, + PostSortType, RegistrationMode, - SortType, }; use chrono::{DateTime, Utc}; 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(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Default)] @@ -27,10 +28,6 @@ pub struct LocalSite { pub site_id: SiteId, /// True if the site is set up. pub site_setup: bool, - /// Whether downvotes are enabled. - pub enable_downvotes: bool, - /// Whether NSFW is enabled. - pub enable_nsfw: bool, /// Whether only admins can create communities. pub community_creation_admin_only: bool, /// Whether emails are required. @@ -68,39 +65,79 @@ pub struct LocalSite { pub federation_signed_fetch: bool, /// Default value for [LocalSite.post_listing_mode] pub default_post_listing_mode: PostListingMode, - /// Default value for [LocalUser.post_listing_mode] - pub default_sort_type: SortType, + /// Default value for [LocalUser.post_sort_type] + pub default_post_sort_type: PostSortType, + /// Default value for [LocalUser.comment_sort_type] + pub default_comment_sort_type: CommentSortType, + /// Whether or not external auth methods can auto-register users. + pub oauth_registration: bool, + /// What kind of post upvotes your site allows. + pub post_upvotes: FederationMode, + /// What kind of post downvotes your site allows. + pub post_downvotes: FederationMode, + /// What kind of comment upvotes your site allows. + pub comment_upvotes: FederationMode, + /// What kind of comment downvotes your site allows. + pub comment_downvotes: FederationMode, } -#[derive(Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable))] #[cfg_attr(feature = "full", diesel(table_name = local_site))] pub struct LocalSiteInsertForm { - #[builder(!default)] pub site_id: SiteId, + #[new(default)] pub site_setup: Option, - pub enable_downvotes: Option, - pub enable_nsfw: Option, + #[new(default)] pub community_creation_admin_only: Option, + #[new(default)] pub require_email_verification: Option, + #[new(default)] pub application_question: Option, + #[new(default)] pub private_instance: Option, + #[new(default)] pub default_theme: Option, + #[new(default)] pub default_post_listing_type: Option, + #[new(default)] pub legal_information: Option, + #[new(default)] pub hide_modlog_mod_names: Option, + #[new(default)] pub application_email_admins: Option, + #[new(default)] pub slur_filter_regex: Option, + #[new(default)] pub actor_name_max_length: Option, + #[new(default)] pub federation_enabled: Option, + #[new(default)] pub captcha_enabled: Option, + #[new(default)] pub captcha_difficulty: Option, + #[new(default)] pub registration_mode: Option, + #[new(default)] pub reports_email_admins: Option, + #[new(default)] pub federation_signed_fetch: Option, + #[new(default)] pub default_post_listing_mode: Option, - pub default_sort_type: Option, + #[new(default)] + pub default_post_sort_type: Option, + #[new(default)] + pub default_comment_sort_type: Option, + #[new(default)] + pub oauth_registration: Option, + #[new(default)] + pub post_upvotes: Option, + #[new(default)] + pub post_downvotes: Option, + #[new(default)] + pub comment_upvotes: Option, + #[new(default)] + pub comment_downvotes: Option, } #[derive(Clone, Default)] @@ -108,8 +145,6 @@ pub struct LocalSiteInsertForm { #[cfg_attr(feature = "full", diesel(table_name = local_site))] pub struct LocalSiteUpdateForm { pub site_setup: Option, - pub enable_downvotes: Option, - pub enable_nsfw: Option, pub community_creation_admin_only: Option, pub require_email_verification: Option, pub application_question: Option>, @@ -129,5 +164,11 @@ pub struct LocalSiteUpdateForm { pub updated: Option>>, pub federation_signed_fetch: Option, pub default_post_listing_mode: Option, - pub default_sort_type: Option, + pub default_post_sort_type: Option, + pub default_comment_sort_type: Option, + pub oauth_registration: Option, + pub post_upvotes: Option, + pub post_downvotes: Option, + pub comment_upvotes: Option, + pub comment_downvotes: Option, } diff --git a/crates/db_schema/src/source/local_site_rate_limit.rs b/crates/db_schema/src/source/local_site_rate_limit.rs index 6ba3df59e..f7f25f5c1 100644 --- a/crates/db_schema/src/source/local_site_rate_limit.rs +++ b/crates/db_schema/src/source/local_site_rate_limit.rs @@ -6,7 +6,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(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] @@ -40,26 +39,38 @@ pub struct LocalSiteRateLimit { pub import_user_settings_per_second: i32, } -#[derive(Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable))] #[cfg_attr(feature = "full", diesel(table_name = local_site_rate_limit))] pub struct LocalSiteRateLimitInsertForm { - #[builder(!default)] pub local_site_id: LocalSiteId, + #[new(default)] pub message: Option, + #[new(default)] pub message_per_second: Option, + #[new(default)] pub post: Option, + #[new(default)] pub post_per_second: Option, + #[new(default)] pub register: Option, + #[new(default)] pub register_per_second: Option, + #[new(default)] pub image: Option, + #[new(default)] pub image_per_second: Option, + #[new(default)] pub comment: Option, + #[new(default)] pub comment_per_second: Option, + #[new(default)] pub search: Option, + #[new(default)] pub search_per_second: Option, + #[new(default)] pub import_user_settings: Option, + #[new(default)] pub import_user_settings_per_second: Option, } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 89bdb1b55..37da70908 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -3,9 +3,10 @@ use crate::schema::local_user; use crate::{ newtypes::{LocalUserId, PersonId}, sensitive::SensitiveString, + CommentSortType, ListingType, PostListingMode, - SortType, + PostSortType, }; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -13,23 +14,24 @@ use serde_with::skip_serializing_none; use ts_rs::TS; #[skip_serializing_none] -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Default)] #[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = local_user))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] +#[serde(default)] /// A local user. pub struct LocalUser { pub id: LocalUserId, /// The person_id for the local user. pub person_id: PersonId, #[serde(skip)] - pub password_encrypted: SensitiveString, + pub password_encrypted: Option, pub email: Option, /// Whether to show NSFW content. pub show_nsfw: bool, pub theme: String, - pub default_sort_type: SortType, + pub default_post_sort_type: PostSortType, pub default_listing_type: ListingType, pub interface_language: String, /// Whether to show avatars. @@ -48,7 +50,6 @@ pub struct LocalUser { /// Open links in a new tab. pub open_links_in_new_tab: bool, pub blur_nsfw: bool, - pub auto_expand: bool, /// Whether infinite scroll is enabled. pub infinite_scroll_enabled: bool, /// Whether the person is an admin. @@ -63,6 +64,7 @@ pub struct LocalUser { pub enable_animated_images: bool, /// Whether to auto-collapse bot comments. pub collapse_bot_comments: bool, + pub default_comment_sort_type: CommentSortType, } #[derive(Clone, derive_new::new)] @@ -70,7 +72,7 @@ pub struct LocalUser { #[cfg_attr(feature = "full", diesel(table_name = local_user))] pub struct LocalUserInsertForm { pub person_id: PersonId, - pub password_encrypted: String, + pub password_encrypted: Option, #[new(default)] pub email: Option, #[new(default)] @@ -78,7 +80,7 @@ pub struct LocalUserInsertForm { #[new(default)] pub theme: Option, #[new(default)] - pub default_sort_type: Option, + pub default_post_sort_type: Option, #[new(default)] pub default_listing_type: Option, #[new(default)] @@ -102,8 +104,6 @@ pub struct LocalUserInsertForm { #[new(default)] pub blur_nsfw: Option, #[new(default)] - pub auto_expand: Option, - #[new(default)] pub infinite_scroll_enabled: Option, #[new(default)] pub admin: Option, @@ -117,6 +117,8 @@ pub struct LocalUserInsertForm { pub enable_animated_images: Option, #[new(default)] pub collapse_bot_comments: Option, + #[new(default)] + pub default_comment_sort_type: Option, } #[derive(Clone, Default)] @@ -127,7 +129,7 @@ pub struct LocalUserUpdateForm { pub email: Option>, pub show_nsfw: Option, pub theme: Option, - pub default_sort_type: Option, + pub default_post_sort_type: Option, pub default_listing_type: Option, pub interface_language: Option, pub show_avatars: Option, @@ -139,7 +141,6 @@ pub struct LocalUserUpdateForm { pub totp_2fa_secret: Option>, pub open_links_in_new_tab: Option, pub blur_nsfw: Option, - pub auto_expand: Option, pub infinite_scroll_enabled: Option, pub admin: Option, pub post_listing_mode: Option, @@ -147,4 +148,5 @@ pub struct LocalUserUpdateForm { pub enable_keyboard_navigation: Option, pub enable_animated_images: Option, pub collapse_bot_comments: Option, + pub default_comment_sort_type: Option, } diff --git a/crates/db_schema/src/source/local_user_vote_display_mode.rs b/crates/db_schema/src/source/local_user_vote_display_mode.rs index 8c6ad95eb..06a433034 100644 --- a/crates/db_schema/src/source/local_user_vote_display_mode.rs +++ b/crates/db_schema/src/source/local_user_vote_display_mode.rs @@ -5,7 +5,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(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)] @@ -28,16 +27,18 @@ pub struct LocalUserVoteDisplayMode { pub upvote_percentage: bool, } -#[derive(Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable))] #[cfg_attr(feature = "full", diesel(table_name = local_user_vote_display_mode))] pub struct LocalUserVoteDisplayModeInsertForm { - #[builder(!default)] pub local_user_id: LocalUserId, + #[new(default)] pub score: Option, + #[new(default)] pub upvotes: Option, + #[new(default)] pub downvotes: Option, + #[new(default)] pub upvote_percentage: Option, } diff --git a/crates/db_schema/src/source/mod.rs b/crates/db_schema/src/source/mod.rs index bbc8aafa2..377c1aaef 100644 --- a/crates/db_schema/src/source/mod.rs +++ b/crates/db_schema/src/source/mod.rs @@ -27,6 +27,8 @@ pub mod local_user; pub mod local_user_vote_display_mode; pub mod login_token; pub mod moderator; +pub mod oauth_account; +pub mod oauth_provider; pub mod password_reset_request; pub mod person; pub mod person_block; diff --git a/crates/db_schema/src/source/oauth_account.rs b/crates/db_schema/src/source/oauth_account.rs new file mode 100644 index 000000000..83b578e22 --- /dev/null +++ b/crates/db_schema/src/source/oauth_account.rs @@ -0,0 +1,32 @@ +use crate::newtypes::{LocalUserId, OAuthProviderId}; +#[cfg(feature = "full")] +use crate::schema::oauth_account; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, TS))] +#[cfg_attr(feature = "full", diesel(table_name = oauth_account))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +/// An auth account method. +pub struct OAuthAccount { + pub local_user_id: LocalUserId, + pub oauth_provider_id: OAuthProviderId, + pub oauth_user_id: String, + pub published: DateTime, + pub updated: Option>, +} + +#[derive(Debug, Clone, derive_new::new)] +#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] +#[cfg_attr(feature = "full", diesel(table_name = oauth_account))] +pub struct OAuthAccountInsertForm { + pub local_user_id: LocalUserId, + pub oauth_provider_id: OAuthProviderId, + pub oauth_user_id: String, +} diff --git a/crates/db_schema/src/source/oauth_provider.rs b/crates/db_schema/src/source/oauth_provider.rs new file mode 100644 index 000000000..75b989805 --- /dev/null +++ b/crates/db_schema/src/source/oauth_provider.rs @@ -0,0 +1,122 @@ +#[cfg(feature = "full")] +use crate::schema::oauth_provider; +use crate::{ + newtypes::{DbUrl, OAuthProviderId}, + sensitive::SensitiveString, +}; +use chrono::{DateTime, Utc}; +use serde::{ + ser::{SerializeStruct, Serializer}, + Deserialize, + Serialize, +}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[skip_serializing_none] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] +#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +/// oauth provider with client_secret - should never be sent to the client +pub struct OAuthProvider { + pub id: OAuthProviderId, + /// The OAuth 2.0 provider name displayed to the user on the Login page + pub display_name: String, + /// The issuer url of the OAUTH provider. + #[cfg_attr(feature = "full", ts(type = "string"))] + pub issuer: DbUrl, + /// The authorization endpoint is used to interact with the resource owner and obtain an + /// authorization grant. This is usually provided by the OAUTH provider. + #[cfg_attr(feature = "full", ts(type = "string"))] + pub authorization_endpoint: DbUrl, + /// The token endpoint is used by the client to obtain an access token by presenting its + /// authorization grant or refresh token. This is usually provided by the OAUTH provider. + #[cfg_attr(feature = "full", ts(type = "string"))] + pub token_endpoint: DbUrl, + /// The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the + /// authenticated End-User. This is defined in the OIDC specification. + #[cfg_attr(feature = "full", ts(type = "string"))] + pub userinfo_endpoint: DbUrl, + /// The OAuth 2.0 claim containing the unique user ID returned by the provider. Usually this + /// should be set to "sub". + pub id_claim: String, + /// The client_id is provided by the OAuth 2.0 provider and is a unique identifier to this + /// service + pub client_id: String, + /// The client_secret is provided by the OAuth 2.0 provider and is used to authenticate this + /// service with the provider + #[serde(skip)] + pub client_secret: SensitiveString, + /// Lists the scopes requested from users. Users will have to grant access to the requested scope + /// at sign up. + pub scopes: String, + /// Automatically sets email as verified on registration + pub auto_verify_email: bool, + /// Allows linking an OAUTH account to an existing user account by matching emails + pub account_linking_enabled: bool, + /// switch to enable or disable an oauth provider + pub enabled: bool, + pub published: DateTime, + pub updated: Option>, +} + +#[derive(Clone, PartialEq, Eq, Debug, Deserialize)] +#[serde(transparent)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +// A subset of OAuthProvider used for public requests, for example to display the OAUTH buttons on +// the login page +pub struct PublicOAuthProvider(pub OAuthProvider); + +impl Serialize for PublicOAuthProvider { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("PublicOAuthProvider", 5)?; + state.serialize_field("id", &self.0.id)?; + state.serialize_field("display_name", &self.0.display_name)?; + state.serialize_field("authorization_endpoint", &self.0.authorization_endpoint)?; + state.serialize_field("client_id", &self.0.client_id)?; + state.serialize_field("scopes", &self.0.scopes)?; + state.end() + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] +#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))] +pub struct OAuthProviderInsertForm { + pub display_name: String, + pub issuer: DbUrl, + pub authorization_endpoint: DbUrl, + pub token_endpoint: DbUrl, + pub userinfo_endpoint: DbUrl, + pub id_claim: String, + pub client_id: String, + pub client_secret: String, + pub scopes: String, + pub auto_verify_email: Option, + pub account_linking_enabled: Option, + pub enabled: Option, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] +#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))] +pub struct OAuthProviderUpdateForm { + pub display_name: Option, + pub authorization_endpoint: Option, + pub token_endpoint: Option, + pub userinfo_endpoint: Option, + pub id_claim: Option, + pub client_secret: Option, + pub scopes: Option, + pub auto_verify_email: Option, + pub account_linking_enabled: Option, + pub enabled: Option, + pub updated: Option>>, +} diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 332b46eb5..c3aeeb4d7 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -48,8 +48,6 @@ pub struct Person { #[cfg_attr(feature = "full", ts(skip))] #[serde(skip, default = "placeholder_apub_url")] pub inbox_url: DbUrl, - #[serde(skip)] - pub shared_inbox_url: Option, /// A matrix id, usually given an @person:matrix.org pub matrix_user_id: Option, /// Whether the person is a bot account. @@ -93,8 +91,6 @@ pub struct PersonInsertForm { #[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, @@ -119,7 +115,6 @@ pub struct PersonUpdateForm { pub banner: Option>, pub deleted: Option, pub inbox_url: Option, - pub shared_inbox_url: Option>, pub matrix_user_id: Option>, pub bot_account: Option, pub ban_expires: Option>>, diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 541d9c307..3819bd773 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -6,7 +6,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)] @@ -58,38 +57,57 @@ pub struct Post { pub url_content_type: Option, /// An optional alt_text, usable for image posts. pub alt_text: Option, + /// Time at which the post will be published. None means publish immediately. + pub scheduled_publish_time: Option>, } -#[derive(Debug, Clone, TypedBuilder)] -#[builder(field_defaults(default))] +#[derive(Debug, Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = post))] pub struct PostInsertForm { - #[builder(!default)] pub name: String, - #[builder(!default)] pub creator_id: PersonId, - #[builder(!default)] pub community_id: CommunityId, + #[new(default)] pub nsfw: Option, + #[new(default)] pub url: Option, + #[new(default)] pub body: Option, + #[new(default)] pub removed: Option, + #[new(default)] pub locked: Option, + #[new(default)] pub updated: Option>, + #[new(default)] pub published: Option>, + #[new(default)] pub deleted: Option, + #[new(default)] pub embed_title: Option, + #[new(default)] pub embed_description: Option, + #[new(default)] pub embed_video_url: Option, + #[new(default)] pub thumbnail_url: Option, + #[new(default)] pub ap_id: Option, + #[new(default)] pub local: Option, + #[new(default)] pub language_id: Option, + #[new(default)] pub featured_community: Option, + #[new(default)] pub featured_local: Option, + #[new(default)] pub url_content_type: Option, + #[new(default)] pub alt_text: Option, + #[new(default)] + pub scheduled_publish_time: Option>, } #[derive(Debug, Clone, Default)] @@ -116,6 +134,7 @@ pub struct PostUpdateForm { pub featured_local: Option, pub url_content_type: Option>, pub alt_text: Option>, + pub scheduled_publish_time: Option>>, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_schema/src/source/private_message.rs b/crates/db_schema/src/source/private_message.rs index 94a600921..8afaa14f1 100644 --- a/crates/db_schema/src/source/private_message.rs +++ b/crates/db_schema/src/source/private_message.rs @@ -6,7 +6,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)] @@ -35,22 +34,24 @@ pub struct PrivateMessage { pub local: bool, } -#[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 = private_message))] pub struct PrivateMessageInsertForm { - #[builder(!default)] pub creator_id: PersonId, - #[builder(!default)] pub recipient_id: PersonId, - #[builder(!default)] pub content: String, + #[new(default)] pub deleted: Option, + #[new(default)] pub read: Option, + #[new(default)] pub published: Option>, + #[new(default)] pub updated: Option>, + #[new(default)] pub ap_id: Option, + #[new(default)] pub local: Option, } diff --git a/crates/db_schema/src/source/site.rs b/crates/db_schema/src/source/site.rs index 325bff97c..0ec4043e4 100644 --- a/crates/db_schema/src/source/site.rs +++ b/crates/db_schema/src/source/site.rs @@ -9,7 +9,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(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] @@ -47,25 +46,33 @@ pub struct Site { pub content_warning: Option, } -#[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 = site))] pub struct SiteInsertForm { - #[builder(!default)] pub name: String, - pub sidebar: Option, - pub updated: Option>, - pub icon: Option, - pub banner: Option, - pub description: Option, - pub actor_id: Option, - pub last_refreshed_at: Option>, - pub inbox_url: Option, - pub private_key: Option, - pub public_key: Option, - #[builder(!default)] pub instance_id: InstanceId, + #[new(default)] + pub sidebar: Option, + #[new(default)] + pub updated: Option>, + #[new(default)] + pub icon: Option, + #[new(default)] + pub banner: Option, + #[new(default)] + pub description: Option, + #[new(default)] + pub actor_id: Option, + #[new(default)] + pub last_refreshed_at: Option>, + #[new(default)] + pub inbox_url: Option, + #[new(default)] + pub private_key: Option, + #[new(default)] + pub public_key: Option, + #[new(default)] pub content_warning: Option, } diff --git a/crates/db_schema/src/source/tagline.rs b/crates/db_schema/src/source/tagline.rs index dbc904a78..05f7e0520 100644 --- a/crates/db_schema/src/source/tagline.rs +++ b/crates/db_schema/src/source/tagline.rs @@ -1,4 +1,3 @@ -use crate::newtypes::LocalSiteId; #[cfg(feature = "full")] use crate::schema::tagline; use chrono::{DateTime, Utc}; @@ -9,21 +8,13 @@ use ts_rs::TS; #[skip_serializing_none] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable, TS) -)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = tagline))] -#[cfg_attr( - feature = "full", - diesel(belongs_to(crate::source::local_site::LocalSite)) -)] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A tagline, shown at the top of your site. pub struct Tagline { pub id: i32, - pub local_site_id: LocalSiteId, pub content: String, pub published: DateTime, pub updated: Option>, @@ -32,8 +23,14 @@ pub struct Tagline { #[derive(Clone, Default)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = tagline))] -pub struct TaglineForm { - pub local_site_id: LocalSiteId, +pub struct TaglineInsertForm { pub content: String, - pub updated: Option>, +} + +#[derive(Clone, Default)] +#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] +#[cfg_attr(feature = "full", diesel(table_name = tagline))] +pub struct TaglineUpdateForm { + pub content: String, + pub updated: DateTime, } diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 2b0da6c7f..74f5ea009 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,5 +1,4 @@ use crate::{ - diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, utils::{get_conn, DbPool}, }; @@ -43,10 +42,10 @@ where async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result; - async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result, Error> { + async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result { let query: Find = Self::table().find(id); let conn = &mut *get_conn(pool).await?; - query.first(conn).await.optional() + query.first(conn).await } /// when you want to null out a column, you have to send Some(None)), since sending None means you diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index b71c43495..1e56563bc 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,4 +1,4 @@ -use crate::{newtypes::DbUrl, CommentSortType, SortType}; +use crate::{newtypes::DbUrl, CommentSortType, PostSortType}; use chrono::{DateTime, TimeDelta, Utc}; use deadpool::Runtime; use diesel::{ @@ -13,7 +13,6 @@ use diesel::{ }, sql_types::{self, Timestamptz}, IntoSql, - OptionalExtension, }; use diesel_async::{ pg::AsyncPgConnection, @@ -30,7 +29,7 @@ use i_love_jesus::CursorKey; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, settings::SETTINGS, - utils::validation::clean_url_params, + utils::validation::clean_url, }; use regex::Regex; use rustls::{ @@ -288,7 +287,7 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } -/// Takes an API text input, and converts it to an optional diesel DB update. +/// Takes an API optional 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 @@ -298,6 +297,17 @@ pub fn diesel_string_update(opt: Option<&str>) -> Option> { } } +/// Takes an API optional text input, and converts it to an optional diesel DB update (for non +/// nullable properties). +pub fn diesel_required_string_update(opt: Option<&str>) -> Option { + match opt { + // An empty string is no change + Some("") => None, + Some(str) => Some(str.into()), + None => None, + } +} + /// 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>> { @@ -305,7 +315,20 @@ pub fn diesel_url_update(opt: Option<&str>) -> LemmyResult> // An empty string is an erase Some("") => Ok(Some(None)), Some(str_url) => Url::parse(str_url) - .map(|u| Some(Some(clean_url_params(&u).into()))) + .map(|u| Some(Some(clean_url(&u).into()))) + .with_lemmy_type(LemmyErrorType::InvalidUrl), + None => Ok(None), + } +} + +/// Takes an optional API URL-type input, and converts it to an optional diesel DB update (for non +/// nullable properties). Also cleans the url params. +pub fn diesel_required_url_update(opt: Option<&str>) -> LemmyResult> { + match opt { + // An empty string is no change + Some("") => Ok(None), + Some(str_url) => Url::parse(str_url) + .map(|u| Some(clean_url(&u).into())) .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } @@ -316,7 +339,7 @@ pub fn diesel_url_update(opt: Option<&str>) -> LemmyResult> pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult> { match opt { Some(str_url) => Url::parse(str_url) - .map(|u| Some(clean_url_params(&u).into())) + .map(|u| Some(clean_url(&u).into())) .with_lemmy_type(LemmyErrorType::InvalidUrl), None => Ok(None), } @@ -457,23 +480,15 @@ pub fn naive_now() -> DateTime { Utc::now() } -pub fn post_to_comment_sort_type(sort: SortType) -> CommentSortType { +pub fn post_to_comment_sort_type(sort: PostSortType) -> CommentSortType { + use PostSortType::*; match sort { - SortType::Active | SortType::Hot | SortType::Scaled => CommentSortType::Hot, - SortType::New | SortType::NewComments | SortType::MostComments => CommentSortType::New, - SortType::Old => CommentSortType::Old, - SortType::Controversial => CommentSortType::Controversial, - SortType::TopHour - | SortType::TopSixHour - | SortType::TopTwelveHour - | SortType::TopDay - | SortType::TopAll - | SortType::TopWeek - | SortType::TopYear - | SortType::TopMonth - | SortType::TopThreeMonths - | SortType::TopSixMonths - | SortType::TopNineMonths => CommentSortType::Top, + Active | Hot | Scaled => CommentSortType::Hot, + New | NewComments | MostComments => CommentSortType::New, + Old => CommentSortType::Old, + Controversial => CommentSortType::Controversial, + TopHour | TopSixHour | TopTwelveHour | TopDay | TopAll | TopWeek | TopYear | TopMonth + | TopThreeMonths | TopSixMonths | TopNineMonths => CommentSortType::Top, } } @@ -558,12 +573,12 @@ impl Queries { self, pool: &'a mut DbPool<'_>, args: Args, - ) -> Result, DieselError> + ) -> Result where RF: ReadFn<'a, T, Args>, { let conn = get_conn(pool).await?; - (self.read_fn)(conn, args).await.optional() + (self.read_fn)(conn, args).await } pub async fn list<'a, T, Args>( @@ -580,7 +595,6 @@ impl Queries { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 1866aaee9..c65e121e2 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -192,7 +192,7 @@ impl CommentReportView { pool: &mut DbPool<'_>, report_id: CommentReportId, my_person_id: PersonId, - ) -> Result, Error> { + ) -> Result { queries().read(pool, (report_id, my_person_id)).await } @@ -259,8 +259,7 @@ impl CommentReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{ @@ -284,27 +283,24 @@ mod tests { CommunityVisibility, SubscribedType, }; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] #[serial] - async fn test_crud() { + 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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_crv"); - let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); + let inserted_timmy = Person::create(pool, &new_person).await?; let new_local_user = LocalUserInsertForm::test_form(inserted_timmy.id); - let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![]) - .await - .unwrap(); + let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?; let timmy_view = LocalUserView { local_user: timmy_local_user, local_user_vote_display_mode: LocalUserVoteDisplayMode::default(), @@ -314,21 +310,20 @@ mod tests { let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_crv"); - let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); + let inserted_sara = Person::create(pool, &new_person_2).await?; // Add a third person, since new ppl can only report something once. let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_crv"); - let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap(); + let inserted_jessica = Person::create(pool, &new_person_3).await?; - let new_community = CommunityInsertForm::builder() - .name("test community crv".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_community = CommunityInsertForm::new( + inserted_instance.id, + "test community crv".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; // Make timmy a mod let timmy_moderator_form = CommunityModeratorForm { @@ -336,25 +331,22 @@ mod tests { person_id: inserted_timmy.id, }; - let _inserted_moderator = CommunityModerator::join(pool, &timmy_moderator_form) - .await - .unwrap(); + let _inserted_moderator = CommunityModerator::join(pool, &timmy_moderator_form).await?; - let new_post = PostInsertForm::builder() - .name("A test post crv".into()) - .creator_id(inserted_timmy.id) - .community_id(inserted_community.id) - .build(); + let new_post = PostInsertForm::new( + "A test post crv".into(), + inserted_timmy.id, + inserted_community.id, + ); - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let inserted_post = Post::create(pool, &new_post).await?; - let comment_form = CommentInsertForm::builder() - .content("A test comment 32".into()) - .creator_id(inserted_timmy.id) - .post_id(inserted_post.id) - .build(); - - let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); + let comment_form = CommentInsertForm::new( + inserted_timmy.id, + inserted_post.id, + "A test comment 32".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; // sara reports let sara_report_form = CommentReportForm { @@ -364,9 +356,7 @@ mod tests { reason: "from sara".into(), }; - let inserted_sara_report = CommentReport::report(pool, &sara_report_form) - .await - .unwrap(); + let inserted_sara_report = CommentReport::report(pool, &sara_report_form).await?; // jessica reports let jessica_report_form = CommentReportForm { @@ -376,20 +366,12 @@ mod tests { reason: "from jessica".into(), }; - let inserted_jessica_report = CommentReport::report(pool, &jessica_report_form) - .await - .unwrap(); + let inserted_jessica_report = CommentReport::report(pool, &jessica_report_form).await?; - let agg = CommentAggregates::read(pool, inserted_comment.id) - .await - .unwrap() - .unwrap(); + let agg = CommentAggregates::read(pool, inserted_comment.id).await?; let read_jessica_report_view = - CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) - .await - .unwrap() - .unwrap(); + CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id).await?; let expected_jessica_report_view = CommentReportView { comment_report: inserted_jessica_report.clone(), comment: inserted_comment.clone(), @@ -420,7 +402,6 @@ mod tests { last_refreshed_at: inserted_community.last_refreshed_at, followers_url: inserted_community.followers_url, inbox_url: inserted_community.inbox_url, - shared_inbox_url: inserted_community.shared_inbox_url, moderators_url: inserted_community.moderators_url, featured_url: inserted_community.featured_url, instance_id: inserted_instance.id, @@ -441,7 +422,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_jessica.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: inserted_instance.id, @@ -464,7 +444,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_timmy.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: inserted_instance.id, @@ -506,7 +485,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_sara.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: inserted_instance.id, @@ -518,8 +496,7 @@ mod tests { // Do a batch read of timmys reports let reports = CommentReportQuery::default() .list(pool, &timmy_view) - .await - .unwrap(); + .await?; assert_eq!( reports, @@ -530,20 +507,14 @@ mod tests { ); // Make sure the counts are correct - let report_count = CommentReportView::get_report_count(pool, inserted_timmy.id, false, None) - .await - .unwrap(); + let report_count = + CommentReportView::get_report_count(pool, inserted_timmy.id, false, None).await?; assert_eq!(2, report_count); // Try to resolve the report - CommentReport::resolve(pool, inserted_jessica_report.id, inserted_timmy.id) - .await - .unwrap(); + CommentReport::resolve(pool, inserted_jessica_report.id, inserted_timmy.id).await?; let read_jessica_report_view_after_resolve = - CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) - .await - .unwrap() - .unwrap(); + CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id).await?; let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view; expected_jessica_report_view_after_resolve @@ -575,7 +546,6 @@ mod tests { private_key: inserted_timmy.private_key.clone(), public_key: inserted_timmy.public_key.clone(), last_refreshed_at: inserted_timmy.last_refreshed_at, - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: inserted_instance.id, @@ -593,24 +563,21 @@ mod tests { ..Default::default() } .list(pool, &timmy_view) - .await - .unwrap(); + .await?; assert_eq!(reports_after_resolve[0], expected_sara_report_view); assert_eq!(reports_after_resolve.len(), 1); // Make sure the counts are correct let report_count_after_resolved = - CommentReportView::get_report_count(pool, inserted_timmy.id, false, None) - .await - .unwrap(); + CommentReportView::get_report_count(pool, inserted_timmy.id, false, None).await?; assert_eq!(1, report_count_after_resolved); - Person::delete(pool, inserted_timmy.id).await.unwrap(); - Person::delete(pool, inserted_sara.id).await.unwrap(); - Person::delete(pool, inserted_jessica.id).await.unwrap(); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Person::delete(pool, inserted_timmy.id).await?; + Person::delete(pool, inserted_sara.id).await?; + Person::delete(pool, inserted_jessica.id).await?; + Community::delete(pool, inserted_community.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index e2752a0c7..ac0e03f65 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -35,7 +35,7 @@ use lemmy_db_schema::{ person_block, post, }, - source::local_user::LocalUser, + source::{local_user::LocalUser, site::Site}, utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, CommentSortType, ListingType, @@ -43,7 +43,7 @@ use lemmy_db_schema::{ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentView, (CommentId, Option<&'a LocalUser>)>, - impl ListFn<'a, CommentView, CommentQuery<'a>>, + impl ListFn<'a, CommentView, (CommentQuery<'a>, &'a Site)>, > { let is_creator_banned_from_community = exists( community_person_ban::table.filter( @@ -182,7 +182,7 @@ fn queries<'a>() -> Queries< query.first(&mut conn).await }; - let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move { + let list = move |mut conn: DbConn<'a>, (options, site): (CommentQuery<'a>, &'a Site)| async move { // The left join below will return None in this case let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); let local_user_id_join = options @@ -216,32 +216,30 @@ fn queries<'a>() -> Queries< query = query.filter(post::community_id.eq(community_id)); } - if let Some(listing_type) = options.listing_type { - let is_subscribed = exists( - community_follower::table.filter( - post::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id_join)), - ), - ); + let is_subscribed = exists( + community_follower::table.filter( + post::community_id + .eq(community_follower::community_id) + .and(community_follower::person_id.eq(person_id_join)), + ), + ); - match listing_type { - 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)) - .filter(community::hidden.eq(false).or(is_subscribed)) - } - ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), - ListingType::ModeratorView => { - query = query.filter(exists( - community_moderator::table.filter( - post::community_id - .eq(community_moderator::community_id) - .and(community_moderator::person_id.eq(person_id_join)), - ), - )); - } + match options.listing_type.unwrap_or_default() { + 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)) + .filter(community::hidden.eq(false).or(is_subscribed)) + } + ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::ModeratorView => { + query = query.filter(exists( + community_moderator::table.filter( + post::community_id + .eq(community_moderator::community_id) + .and(community_moderator::person_id.eq(person_id_join)), + ), + )); } } @@ -295,6 +293,12 @@ fn queries<'a>() -> Queries< query = query.filter(not(is_creator_blocked(person_id_join))); }; + if !options.local_user.show_nsfw(site) { + query = query + .filter(post::nsfw.eq(false)) + .filter(community::nsfw.eq(false)); + }; + query = options.local_user.visible_communities_only(query); // A Max depth given means its a tree fetch @@ -364,21 +368,18 @@ impl CommentView { pool: &mut DbPool<'_>, comment_id: CommentId, my_local_user: Option<&'a LocalUser>, - ) -> Result, Error> { + ) -> Result { // If a person is given, then my_vote (res.9), if None, should be 0, not null // Necessary to differentiate between other person's votes - if let Ok(Some(res)) = queries().read(pool, (comment_id, my_local_user)).await { - let mut new_view = res.clone(); - if my_local_user.is_some() && res.my_vote.is_none() { - new_view.my_vote = Some(0); - } - if res.comment.deleted || res.comment.removed { - new_view.comment.content = String::new(); - } - Ok(Some(new_view)) - } else { - Ok(None) + let res = queries().read(pool, (comment_id, my_local_user)).await?; + let mut new_view = res.clone(); + if my_local_user.is_some() && res.my_vote.is_none() { + new_view.my_vote = Some(0); } + if res.comment.deleted || res.comment.removed { + new_view.comment.content = String::new(); + } + Ok(new_view) } } @@ -401,10 +402,10 @@ pub struct CommentQuery<'a> { } impl<'a> CommentQuery<'a> { - pub async fn list(self, pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { Ok( queries() - .list(pool, self) + .list(pool, (self, site)) .await? .into_iter() .map(|mut c| { @@ -419,7 +420,7 @@ impl<'a> CommentQuery<'a> { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{ @@ -457,14 +458,15 @@ mod tests { local_user_vote_display_mode::LocalUserVoteDisplayMode, person::{Person, PersonInsertForm}, person_block::{PersonBlock, PersonBlockForm}, - post::{Post, PostInsertForm}, + post::{Post, PostInsertForm, PostUpdateForm}, + site::{Site, SiteInsertForm}, }, traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable}, utils::{build_db_pool_for_tests, RANK_DEFAULT}, CommunityVisibility, SubscribedType, }; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -477,6 +479,7 @@ mod tests { timmy_local_user_view: LocalUserView, inserted_sara_person: Person, inserted_community: Community, + site: Site, } async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { @@ -491,23 +494,21 @@ mod tests { 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()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community 5".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); let inserted_community = Community::create(pool, &new_community).await?; - let new_post = PostInsertForm::builder() - .name("A test post 2".into()) - .creator_id(inserted_timmy_person.id) - .community_id(inserted_community.id) - .build(); - + let new_post = PostInsertForm::new( + "A test post 2".into(), + inserted_timmy_person.id, + inserted_community.id, + ); let inserted_post = Post::create(pool, &new_post).await?; - let english_id = Language::read_id_from_code(pool, Some("en")).await?; + let english_id = Language::read_id_from_code(pool, "en").await?; // Create a comment tree with this hierarchy // 0 @@ -517,65 +518,70 @@ mod tests { // 3 4 // \ // 5 - let comment_form_0 = CommentInsertForm::builder() - .content("Comment 0".into()) - .creator_id(inserted_timmy_person.id) - .post_id(inserted_post.id) - .language_id(english_id) - .build(); + let comment_form_0 = CommentInsertForm { + language_id: Some(english_id), + ..CommentInsertForm::new( + inserted_timmy_person.id, + inserted_post.id, + "Comment 0".into(), + ) + }; 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()) - .creator_id(inserted_sara_person.id) - .post_id(inserted_post.id) - .language_id(english_id) - .build(); - + let comment_form_1 = CommentInsertForm { + language_id: Some(english_id), + ..CommentInsertForm::new( + inserted_sara_person.id, + inserted_post.id, + "Comment 1, A test blocked comment".into(), + ) + }; 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?; - let comment_form_2 = CommentInsertForm::builder() - .content("Comment 2".into()) - .creator_id(inserted_timmy_person.id) - .post_id(inserted_post.id) - .language_id(finnish_id) - .build(); + let finnish_id = Language::read_id_from_code(pool, "fi").await?; + let comment_form_2 = CommentInsertForm { + language_id: Some(finnish_id), + ..CommentInsertForm::new( + inserted_timmy_person.id, + inserted_post.id, + "Comment 2".into(), + ) + }; 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()) - .creator_id(inserted_timmy_person.id) - .post_id(inserted_post.id) - .language_id(english_id) - .build(); - + let comment_form_3 = CommentInsertForm { + language_id: Some(english_id), + ..CommentInsertForm::new( + inserted_timmy_person.id, + inserted_post.id, + "Comment 3".into(), + ) + }; let _inserted_comment_3 = Comment::create(pool, &comment_form_3, Some(&inserted_comment_1.path)).await?; - let polish_id = Language::read_id_from_code(pool, Some("pl")) - .await? - .ok_or(LemmyErrorType::LanguageNotAllowed)?; - let comment_form_4 = CommentInsertForm::builder() - .content("Comment 4".into()) - .creator_id(inserted_timmy_person.id) - .post_id(inserted_post.id) - .language_id(Some(polish_id)) - .build(); + let polish_id = Language::read_id_from_code(pool, "pl").await?; + let comment_form_4 = CommentInsertForm { + language_id: Some(polish_id), + ..CommentInsertForm::new( + inserted_timmy_person.id, + inserted_post.id, + "Comment 4".into(), + ) + }; 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()) - .creator_id(inserted_timmy_person.id) - .post_id(inserted_post.id) - .build(); - + let comment_form_5 = CommentInsertForm::new( + inserted_timmy_person.id, + inserted_post.id, + "Comment 5".into(), + ); let _inserted_comment_5 = Comment::create(pool, &comment_form_5, Some(&inserted_comment_4.path)).await?; @@ -595,7 +601,6 @@ mod tests { let comment_like_form = CommentLikeForm { comment_id: inserted_comment_0.id, - post_id: inserted_post.id, person_id: inserted_timmy_person.id, score: 1, }; @@ -608,6 +613,8 @@ mod tests { person: inserted_timmy_person.clone(), counts: Default::default(), }; + let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id); + let site = Site::create(pool, &site_form).await?; Ok(Data { inserted_instance, inserted_comment_0, @@ -617,6 +624,7 @@ mod tests { timmy_local_user_view, inserted_sara_person, inserted_community, + site, }) } @@ -637,14 +645,12 @@ mod tests { post_id: (Some(data.inserted_post.id)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!( - &expected_comment_view_no_person, - read_comment_views_no_person - .first() - .ok_or(LemmyErrorType::CouldntFindComment)? + Some(&expected_comment_view_no_person), + read_comment_views_no_person.first() ); let read_comment_views_with_person = CommentQuery { @@ -653,7 +659,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!( @@ -669,8 +675,7 @@ mod tests { data.inserted_comment_1.id, Some(&data.timmy_local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; // Make sure block set the creator blocked assert!(read_comment_from_blocked_person.creator_blocked); @@ -695,7 +700,6 @@ mod tests { // Like a new comment let comment_like_form = CommentLikeForm { comment_id: data.inserted_comment_1.id, - post_id: data.inserted_post.id, person_id: data.timmy_local_user_view.person.id, score: 1, }; @@ -706,7 +710,7 @@ mod tests { liked_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await? .into_iter() .map(|c| c.comment.content) @@ -722,7 +726,7 @@ mod tests { disliked_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert!(read_disliked_comment_views.is_empty()); @@ -743,7 +747,7 @@ mod tests { parent_path: (Some(top_path)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; let child_path = data.inserted_comment_1.path.clone(); @@ -752,7 +756,7 @@ mod tests { parent_path: (Some(child_path)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure the comment parent-limited fetch is correct @@ -772,7 +776,7 @@ mod tests { max_depth: (Some(1)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure a depth limited one only has the top comment @@ -790,7 +794,7 @@ mod tests { sort: (Some(CommentSortType::New)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure a depth limited one, and given child comment 1, has 3 @@ -816,14 +820,12 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(5, all_languages); // change user lang to finnish, should only show one post in finnish and one undetermined - let finnish_id = Language::read_id_from_code(pool, Some("fi")) - .await? - .ok_or(LemmyErrorType::LanguageNotAllowed)?; + let finnish_id = Language::read_id_from_code(pool, "fi").await?; LocalUserLanguage::update( pool, vec![finnish_id], @@ -834,7 +836,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(2, finnish_comments); let finnish_comment = finnish_comments @@ -842,11 +844,8 @@ mod tests { .find(|c| c.comment.language_id == finnish_id); assert!(finnish_comment.is_some()); assert_eq!( - data.inserted_comment_2.content, - finnish_comment - .ok_or(LemmyErrorType::CouldntFindComment)? - .comment - .content + Some(&data.inserted_comment_2.content), + finnish_comment.map(|c| &c.comment.content) ); // now show all comments with undetermined language (which is the default value) @@ -860,7 +859,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(1, undetermined_comment); @@ -884,7 +883,7 @@ mod tests { post_id: Some(data.inserted_comment_2.post_id), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(comments[0].comment.id, data.inserted_comment_2.id); assert!(comments[0].comment.distinguished); @@ -913,7 +912,7 @@ mod tests { sort: (Some(CommentSortType::Old)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(comments[1].creator.name, "sara"); @@ -934,7 +933,7 @@ mod tests { sort: (Some(CommentSortType::Old)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Timmy is an admin, and make sure that field is true @@ -974,7 +973,7 @@ mod tests { saved_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // There should only be two comments @@ -1004,14 +1003,13 @@ mod tests { LocalUser::delete(pool, data.timmy_local_user_view.local_user.id).await?; Person::delete(pool, data.inserted_sara_person.id).await?; Instance::delete(pool, data.inserted_instance.id).await?; + Site::delete(pool, data.site.id).await?; Ok(()) } async fn expected_comment_view(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult { - let agg = CommentAggregates::read(pool, data.inserted_comment_0.id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let agg = CommentAggregates::read(pool, data.inserted_comment_0.id).await?; Ok(CommentView { creator_banned_from_community: false, banned_from_community: false, @@ -1051,7 +1049,6 @@ mod tests { banner: None, updated: None, inbox_url: data.timmy_local_user_view.person.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: data.inserted_instance.id, @@ -1083,6 +1080,7 @@ mod tests { featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }, community: Community { id: data.inserted_community.id, @@ -1106,7 +1104,6 @@ mod tests { last_refreshed_at: data.inserted_community.last_refreshed_at, followers_url: data.inserted_community.followers_url.clone(), inbox_url: data.inserted_community.inbox_url.clone(), - shared_inbox_url: data.inserted_community.shared_inbox_url.clone(), moderators_url: data.inserted_community.moderators_url.clone(), featured_url: data.inserted_community.featured_url.clone(), visibility: CommunityVisibility::Public, @@ -1144,7 +1141,7 @@ mod tests { let unauthenticated_query = CommentQuery { ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(0, unauthenticated_query.len()); @@ -1152,12 +1149,12 @@ mod tests { local_user: Some(&data.timmy_local_user_view.local_user), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(5, authenticated_query.len()); - let unauthenticated_comment = CommentView::read(pool, data.inserted_comment_0.id, None).await?; - assert!(unauthenticated_comment.is_none()); + let unauthenticated_comment = CommentView::read(pool, data.inserted_comment_0.id, None).await; + assert!(unauthenticated_comment.is_err()); let authenticated_comment = CommentView::read( pool, @@ -1204,8 +1201,7 @@ mod tests { data.inserted_comment_0.id, Some(&inserted_banned_from_comm_local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; assert!(comment_view.banned_from_community); @@ -1225,11 +1221,39 @@ mod tests { data.inserted_comment_0.id, Some(&data.timmy_local_user_view.local_user), ) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + .await?; assert!(!comment_view.banned_from_community); cleanup(data, pool).await } + + #[tokio::test] + #[serial] + async fn comment_listings_hide_nsfw() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Mark a post as nsfw + let update_form = PostUpdateForm { + nsfw: Some(true), + ..Default::default() + }; + Post::update(pool, data.inserted_post.id, &update_form).await?; + + // Make sure comments of this post are not returned + let comments = CommentQuery::default().list(&data.site, pool).await?; + assert_eq!(0, comments.len()); + + // Mark site as nsfw + let mut site = data.site.clone(); + site.content_warning = Some("nsfw".to_string()); + + // Now comments of nsfw post are returned + let comments = CommentQuery::default().list(&site, pool).await?; + assert_eq!(6, comments.len()); + + cleanup(data, pool).await + } } diff --git a/crates/db_views/src/custom_emoji_view.rs b/crates/db_views/src/custom_emoji_view.rs index 4d2f1fd85..a346c086d 100644 --- a/crates/db_views/src/custom_emoji_view.rs +++ b/crates/db_views/src/custom_emoji_view.rs @@ -2,10 +2,10 @@ use crate::structs::CustomEmojiView; use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - newtypes::{CustomEmojiId, LocalSiteId}, + newtypes::CustomEmojiId, schema::{custom_emoji, custom_emoji_keyword}, source::{custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword}, - utils::{get_conn, DbPool}, + utils::{get_conn, limit_and_offset, DbPool}, }; use std::collections::HashMap; @@ -35,18 +35,34 @@ impl CustomEmojiView { } } - pub async fn get_all( + pub async fn list( pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, + category: &Option, + page: Option, + limit: Option, + ignore_page_limits: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let emojis = custom_emoji::table - .filter(custom_emoji::local_site_id.eq(for_local_site_id)) + + let mut query = custom_emoji::table .left_join( custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), ) .order(custom_emoji::category) - .then_order_by(custom_emoji::id) + .into_boxed(); + + if !ignore_page_limits { + let (limit, offset) = limit_and_offset(page, limit)?; + query = query.limit(limit).offset(offset); + } + + if let Some(category) = category { + query = query.filter(custom_emoji::category.eq(category)) + } + + query = query.then_order_by(custom_emoji::id); + + let emojis = query .select(( custom_emoji::all_columns, custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) diff --git a/crates/db_views/src/local_user_view.rs b/crates/db_views/src/local_user_view.rs index 0c13b0a68..8d55b96fe 100644 --- a/crates/db_views/src/local_user_view.rs +++ b/crates/db_views/src/local_user_view.rs @@ -3,8 +3,14 @@ use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest}; use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - newtypes::{LocalUserId, PersonId}, - schema::{local_user, local_user_vote_display_mode, person, person_aggregates}, + newtypes::{LocalUserId, OAuthProviderId, PersonId}, + schema::{local_user, local_user_vote_display_mode, oauth_account, person, person_aggregates}, + source::{ + instance::Instance, + local_user::{LocalUser, LocalUserInsertForm}, + person::{Person, PersonInsertForm}, + }, + traits::Crud, utils::{ functions::{coalesce, lower}, DbConn, @@ -23,6 +29,7 @@ enum ReadBy<'a> { Name(&'a str), NameOrEmail(&'a str), Email(&'a str), + OAuthID(OAuthProviderId, &'a str), } enum ListMode { @@ -58,12 +65,21 @@ fn queries<'a>( ), _ => query, }; - query + let query = query .inner_join(local_user_vote_display_mode::table) - .inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id))) - .select(selection) - .first(&mut conn) - .await + .inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id))); + + if let ReadBy::OAuthID(oauth_provider_id, oauth_user_id) = search { + query + .inner_join(oauth_account::table) + .filter(oauth_account::oauth_provider_id.eq(oauth_provider_id)) + .filter(oauth_account::oauth_user_id.eq(oauth_user_id)) + .select(selection) + .first(&mut conn) + .await + } else { + query.select(selection).first(&mut conn).await + } }; let list = move |mut conn: DbConn<'a>, mode: ListMode| async move { @@ -86,43 +102,69 @@ fn queries<'a>( } impl LocalUserView { - pub async fn read( - pool: &mut DbPool<'_>, - local_user_id: LocalUserId, - ) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result { queries().read(pool, ReadBy::Id(local_user_id)).await } - pub async fn read_person( - pool: &mut DbPool<'_>, - person_id: PersonId, - ) -> Result, Error> { + pub async fn read_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { queries().read(pool, ReadBy::Person(person_id)).await } - pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result, Error> { + pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result { queries().read(pool, ReadBy::Name(name)).await } pub async fn find_by_email_or_name( pool: &mut DbPool<'_>, name_or_email: &str, - ) -> Result, Error> { + ) -> Result { queries() .read(pool, ReadBy::NameOrEmail(name_or_email)) .await } - pub async fn find_by_email( - pool: &mut DbPool<'_>, - from_email: &str, - ) -> Result, Error> { + pub async fn find_by_email(pool: &mut DbPool<'_>, from_email: &str) -> Result { queries().read(pool, ReadBy::Email(from_email)).await } + pub async fn find_by_oauth_id( + pool: &mut DbPool<'_>, + oauth_provider_id: OAuthProviderId, + oauth_user_id: &str, + ) -> Result { + queries() + .read(pool, ReadBy::OAuthID(oauth_provider_id, oauth_user_id)) + .await + } + pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result, Error> { queries().list(pool, ListMode::AdminsWithEmails).await } + + pub async fn create_test_user( + pool: &mut DbPool<'_>, + name: &str, + bio: &str, + admin: bool, + ) -> Result { + let instance_id = Instance::read_or_create(pool, "example.com".to_string()) + .await? + .id; + let person_form = PersonInsertForm { + display_name: Some(name.to_owned()), + bio: Some(bio.to_owned()), + ..PersonInsertForm::test_form(instance_id, name) + }; + let person = Person::create(pool, &person_form).await?; + + let user_form = match admin { + true => LocalUserInsertForm::test_form_admin(person.id), + false => LocalUserInsertForm::test_form(person.id), + }; + let local_user = LocalUser::create(pool, &user_form, vec![]).await?; + + LocalUserView::read(pool, local_user.id).await + } } impl FromRequest for LocalUserView { diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 0cd06dd4e..82e4c5d5b 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -220,7 +220,7 @@ impl PostReportView { pool: &mut DbPool<'_>, report_id: PostReportId, my_person_id: PersonId, - ) -> Result, Error> { + ) -> Result { queries().read(pool, (report_id, my_person_id)).await } @@ -284,8 +284,7 @@ impl PostReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{ @@ -306,27 +305,24 @@ mod tests { traits::{Crud, Joinable, Reportable}, 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_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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_prv"); - let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); + let inserted_timmy = Person::create(pool, &new_person).await?; let new_local_user = LocalUserInsertForm::test_form(inserted_timmy.id); - let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![]) - .await - .unwrap(); + let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?; let timmy_view = LocalUserView { local_user: timmy_local_user, local_user_vote_display_mode: LocalUserVoteDisplayMode::default(), @@ -336,21 +332,20 @@ mod tests { let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_prv"); - let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); + let inserted_sara = Person::create(pool, &new_person_2).await?; // Add a third person, since new ppl can only report something once. let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "jessica_prv"); - let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap(); + let inserted_jessica = Person::create(pool, &new_person_3).await?; - let new_community = CommunityInsertForm::builder() - .name("test community prv".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_community = CommunityInsertForm::new( + inserted_instance.id, + "test community prv".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; // Make timmy a mod let timmy_moderator_form = CommunityModeratorForm { @@ -358,17 +353,14 @@ mod tests { person_id: inserted_timmy.id, }; - let _inserted_moderator = CommunityModerator::join(pool, &timmy_moderator_form) - .await - .unwrap(); + let _inserted_moderator = CommunityModerator::join(pool, &timmy_moderator_form).await?; - let new_post = PostInsertForm::builder() - .name("A test post crv".into()) - .creator_id(inserted_timmy.id) - .community_id(inserted_community.id) - .build(); - - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post crv".into(), + inserted_timmy.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; // sara reports let sara_report_form = PostReportForm { @@ -380,15 +372,14 @@ mod tests { reason: "from sara".into(), }; - PostReport::report(pool, &sara_report_form).await.unwrap(); + PostReport::report(pool, &sara_report_form).await?; - let new_post_2 = PostInsertForm::builder() - .name("A test post crv 2".into()) - .creator_id(inserted_timmy.id) - .community_id(inserted_community.id) - .build(); - - let inserted_post_2 = Post::create(pool, &new_post_2).await.unwrap(); + let new_post_2 = PostInsertForm::new( + "A test post crv 2".into(), + inserted_timmy.id, + inserted_community.id, + ); + let inserted_post_2 = Post::create(pool, &new_post_2).await?; // jessica reports let jessica_report_form = PostReportForm { @@ -400,15 +391,10 @@ mod tests { reason: "from jessica".into(), }; - let inserted_jessica_report = PostReport::report(pool, &jessica_report_form) - .await - .unwrap(); + let inserted_jessica_report = PostReport::report(pool, &jessica_report_form).await?; let read_jessica_report_view = - PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) - .await - .unwrap() - .unwrap(); + PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id).await?; assert_eq!( read_jessica_report_view.post_report, @@ -422,31 +408,23 @@ mod tests { assert_eq!(read_jessica_report_view.resolver, None); // Do a batch read of timmys reports - let reports = PostReportQuery::default() - .list(pool, &timmy_view) - .await - .unwrap(); + let reports = PostReportQuery::default().list(pool, &timmy_view).await?; assert_eq!(reports[1].creator.id, inserted_sara.id); assert_eq!(reports[0].creator.id, inserted_jessica.id); // Make sure the counts are correct - let report_count = PostReportView::get_report_count(pool, inserted_timmy.id, false, None) - .await - .unwrap(); + let report_count = + PostReportView::get_report_count(pool, inserted_timmy.id, false, None).await?; assert_eq!(2, report_count); // Pretend the post was removed, and resolve all reports for that object. // This is called manually in the API for post removals PostReport::resolve_all_for_object(pool, inserted_jessica_report.post_id, inserted_timmy.id) - .await - .unwrap(); + .await?; let read_jessica_report_view_after_resolve = - PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) - .await - .unwrap() - .unwrap(); + PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id).await?; assert!(read_jessica_report_view_after_resolve.post_report.resolved); assert_eq!( read_jessica_report_view_after_resolve @@ -455,8 +433,10 @@ mod tests { Some(inserted_timmy.id) ); assert_eq!( - read_jessica_report_view_after_resolve.resolver.unwrap().id, - inserted_timmy.id + read_jessica_report_view_after_resolve + .resolver + .map(|r| r.id), + Some(inserted_timmy.id) ); // Do a batch read of timmys reports @@ -466,24 +446,21 @@ mod tests { ..Default::default() } .list(pool, &timmy_view) - .await - .unwrap(); + .await?; assert_length!(1, reports_after_resolve); assert_eq!(reports_after_resolve[0].creator.id, inserted_sara.id); // Make sure the counts are correct let report_count_after_resolved = - PostReportView::get_report_count(pool, inserted_timmy.id, false, None) - .await - .unwrap(); + PostReportView::get_report_count(pool, inserted_timmy.id, false, None).await?; assert_eq!(1, report_count_after_resolved); - Person::delete(pool, inserted_timmy.id).await.unwrap(); - Person::delete(pool, inserted_sara.id).await.unwrap(); - Person::delete(pool, inserted_jessica.id).await.unwrap(); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Person::delete(pool, inserted_timmy.id).await?; + Person::delete(pool, inserted_sara.id).await?; + Person::delete(pool, inserted_jessica.id).await?; + Community::delete(pool, inserted_community.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 0ec7e0a5d..41b730c46 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -58,9 +58,10 @@ use lemmy_db_schema::{ ReverseTimestampKey, }, ListingType, - SortType, + PostSortType, }; use tracing::debug; +use PostSortType::*; fn queries<'a>() -> Queries< impl ReadFn<'a, PostView, (PostId, Option<&'a LocalUser>, bool)>, @@ -317,11 +318,18 @@ fn queries<'a>() -> Queries< // hide posts from deleted communities query = query.filter(community::deleted.eq(false)); - // only show deleted posts to creator + // only creator can see deleted posts and unpublished scheduled posts if let Some(person_id) = options.local_user.person_id() { query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id))); + query = query.filter( + post::scheduled_publish_time + .is_null() + .or(post::creator_id.eq(person_id)), + ); } else { - query = query.filter(post::deleted.eq(false)); + query = query + .filter(post::deleted.eq(false)) + .filter(post::scheduled_publish_time.is_null()); } // only show removed posts to admin when viewing user profile @@ -338,62 +346,46 @@ fn queries<'a>() -> Queries< query = query.filter(post_aggregates::creator_id.eq(creator_id)); } - if let Some(listing_type) = options.listing_type { - if let Some(person_id) = options.local_user.person_id() { - let is_subscribed = exists( - community_follower::table.filter( - post_aggregates::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id)), + let is_subscribed = exists( + community_follower::table.filter( + post_aggregates::community_id + .eq(community_follower::community_id) + .and(community_follower::person_id.eq(person_id_join)), + ), + ); + match options.listing_type.unwrap_or_default() { + ListingType::Subscribed => query = query.filter(is_subscribed), + ListingType::Local => { + query = query + .filter(community::local.eq(true)) + .filter(community::hidden.eq(false).or(is_subscribed)); + } + ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::ModeratorView => { + query = query.filter(exists( + community_moderator::table.filter( + post::community_id + .eq(community_moderator::community_id) + .and(community_moderator::person_id.eq(person_id_join)), ), - ); - match listing_type { - ListingType::Subscribed => query = query.filter(is_subscribed), - ListingType::Local => { - query = query - .filter(community::local.eq(true)) - .filter(community::hidden.eq(false).or(is_subscribed)); - } - ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), - ListingType::ModeratorView => { - query = query.filter(exists( - community_moderator::table.filter( - post::community_id - .eq(community_moderator::community_id) - .and(community_moderator::person_id.eq(person_id)), - ), - )); - } - } + )); } - // If your person_id is missing, only show local - else { - match listing_type { - ListingType::Local => { - query = query - .filter(community::local.eq(true)) - .filter(community::hidden.eq(false)); - } - _ => query = query.filter(community::hidden.eq(false)), - } - } - } else { - query = query.filter(community::hidden.eq(false)); - } - - if let Some(url_search) = &options.url_search { - query = query.filter(post::url.eq(url_search)); } if let Some(search_term) = &options.search_term { - let searcher = fuzzy_search(search_term); - query = query - .filter( - post::name - .ilike(searcher.clone()) - .or(post::body.ilike(searcher)), - ) + if options.url_only.unwrap_or_default() { + query = query.filter(post::url.eq(search_term)); + } else { + let searcher = fuzzy_search(search_term); + let name_filter = post::name.ilike(searcher.clone()); + let body_filter = post::body.ilike(searcher.clone()); + query = if options.title_only.unwrap_or_default() { + query.filter(name_filter) + } else { + query.filter(name_filter.or(body_filter)) + } .filter(not(post::removed.or(post::deleted))); + } } if !options @@ -507,33 +499,33 @@ fn queries<'a>() -> Queries< let time = |interval| post_aggregates::published.gt(now() - interval); // then use the main sort - query = match options.sort.unwrap_or(SortType::Hot) { - SortType::Active => query.then_desc(key::hot_rank_active), - SortType::Hot => query.then_desc(key::hot_rank), - SortType::Scaled => query.then_desc(key::scaled_rank), - SortType::Controversial => query.then_desc(key::controversy_rank), - SortType::New => query.then_desc(key::published), - SortType::Old => query.then_desc(ReverseTimestampKey(key::published)), - SortType::NewComments => query.then_desc(key::newest_comment_time), - SortType::MostComments => query.then_desc(key::comments), - SortType::TopAll => query.then_desc(key::score), - SortType::TopYear => query.then_desc(key::score).filter(time(1.years())), - SortType::TopMonth => query.then_desc(key::score).filter(time(1.months())), - SortType::TopWeek => query.then_desc(key::score).filter(time(1.weeks())), - SortType::TopDay => query.then_desc(key::score).filter(time(1.days())), - SortType::TopHour => query.then_desc(key::score).filter(time(1.hours())), - SortType::TopSixHour => query.then_desc(key::score).filter(time(6.hours())), - SortType::TopTwelveHour => query.then_desc(key::score).filter(time(12.hours())), - SortType::TopThreeMonths => query.then_desc(key::score).filter(time(3.months())), - SortType::TopSixMonths => query.then_desc(key::score).filter(time(6.months())), - SortType::TopNineMonths => query.then_desc(key::score).filter(time(9.months())), + query = match options.sort.unwrap_or(Hot) { + Active => query.then_desc(key::hot_rank_active), + Hot => query.then_desc(key::hot_rank), + Scaled => query.then_desc(key::scaled_rank), + Controversial => query.then_desc(key::controversy_rank), + New => query.then_desc(key::published), + Old => query.then_desc(ReverseTimestampKey(key::published)), + NewComments => query.then_desc(key::newest_comment_time), + MostComments => query.then_desc(key::comments), + TopAll => query.then_desc(key::score), + TopYear => query.then_desc(key::score).filter(time(1.years())), + TopMonth => query.then_desc(key::score).filter(time(1.months())), + TopWeek => query.then_desc(key::score).filter(time(1.weeks())), + TopDay => query.then_desc(key::score).filter(time(1.days())), + TopHour => query.then_desc(key::score).filter(time(1.hours())), + TopSixHour => query.then_desc(key::score).filter(time(6.hours())), + TopTwelveHour => query.then_desc(key::score).filter(time(12.hours())), + TopThreeMonths => query.then_desc(key::score).filter(time(3.months())), + TopSixMonths => query.then_desc(key::score).filter(time(6.months())), + TopNineMonths => query.then_desc(key::score).filter(time(9.months())), }; // use publish as fallback. especially useful for hot rank which reaches zero after some days. // necessary because old posts can be fetched over federation and inserted with high post id - query = match options.sort.unwrap_or(SortType::Hot) { + query = match options.sort.unwrap_or(Hot) { // A second time-based sort would not be very useful - SortType::New | SortType::Old | SortType::NewComments => query, + New | Old | NewComments => query, _ => query.then_desc(key::published), }; @@ -564,7 +556,7 @@ impl PostView { post_id: PostId, my_local_user: Option<&'a LocalUser>, is_mod_or_admin: bool, - ) -> Result, Error> { + ) -> Result { queries() .read(pool, (post_id, my_local_user, is_mod_or_admin)) .await @@ -589,8 +581,7 @@ impl PaginationCursor { .ok_or_else(err_msg)?, ), ) - .await? - .ok_or_else(err_msg)?; + .await?; Ok(PaginationCursorData(token)) } @@ -605,7 +596,7 @@ pub struct PaginationCursorData(PostAggregates); #[derive(Clone, Default)] pub struct PostQuery<'a> { pub listing_type: Option, - pub sort: Option, + 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 @@ -613,10 +604,11 @@ pub struct PostQuery<'a> { pub community_id_just_for_prefetch: bool, pub local_user: Option<&'a LocalUser>, pub search_term: Option, - pub url_search: Option, + pub url_only: Option, pub saved_only: Option, pub liked_only: Option, pub disliked_only: Option, + pub title_only: Option, pub page: Option, pub limit: Option, pub page_after: Option, @@ -745,6 +737,8 @@ mod tests { comment::{Comment, CommentInsertForm}, community::{ Community, + CommunityFollower, + CommunityFollowerForm, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -760,21 +754,32 @@ mod tests { local_user_vote_display_mode::LocalUserVoteDisplayMode, person::{Person, PersonInsertForm}, person_block::{PersonBlock, PersonBlockForm}, - post::{Post, PostHide, PostInsertForm, PostLike, PostLikeForm, PostRead, PostUpdateForm}, + post::{ + Post, + PostHide, + PostInsertForm, + PostLike, + PostLikeForm, + PostRead, + PostSaved, + PostSavedForm, + PostUpdateForm, + }, site::Site, }, - traits::{Bannable, Blockable, Crud, Joinable, Likeable}, + traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable}, utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT}, CommunityVisibility, - SortType, + PostSortType, SubscribedType, }; - use lemmy_utils::error::{LemmyErrorType, LemmyResult}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; use std::{collections::HashSet, time::Duration}; use url::Url; + const POST_WITH_ANOTHER_TITLE: &str = "Another title"; const POST_BY_BLOCKED_PERSON: &str = "post by blocked person"; const POST_BY_BOT: &str = "post by bot"; const POST: &str = "post"; @@ -797,7 +802,7 @@ mod tests { impl Data { fn default_post_query(&self) -> PostQuery<'_> { PostQuery { - sort: Some(SortType::New), + sort: Some(PostSortType::New), local_user: Some(&self.local_user_view.local_user), ..Default::default() } @@ -824,13 +829,12 @@ mod tests { let inserted_bot = Person::create(pool, &new_bot).await?; - let new_community = CommunityInsertForm::builder() - .name("test_community_3".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); - + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "test_community_3".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); let inserted_community = Community::create(pool, &new_community).await?; // Test a person block, make sure the post query doesn't include their post @@ -845,13 +849,14 @@ mod tests { ) .await?; - let post_from_blocked_person = PostInsertForm::builder() - .name(POST_BY_BLOCKED_PERSON.to_string()) - .creator_id(inserted_blocked_person.id) - .community_id(inserted_community.id) - .language_id(Some(LanguageId(1))) - .build(); - + let post_from_blocked_person = PostInsertForm { + language_id: Some(LanguageId(1)), + ..PostInsertForm::new( + POST_BY_BLOCKED_PERSON.to_string(), + inserted_blocked_person.id, + inserted_community.id, + ) + }; Post::create(pool, &post_from_blocked_person).await?; // block that person @@ -863,22 +868,19 @@ mod tests { PersonBlock::block(pool, &person_block).await?; // A sample post - let new_post = PostInsertForm::builder() - .name(POST.to_string()) - .creator_id(inserted_person.id) - .community_id(inserted_community.id) - .language_id(Some(LanguageId(47))) - .build(); - + let new_post = PostInsertForm { + language_id: Some(LanguageId(47)), + ..PostInsertForm::new(POST.to_string(), inserted_person.id, inserted_community.id) + }; let inserted_post = Post::create(pool, &new_post).await?; - let new_bot_post = PostInsertForm::builder() - .name(POST_BY_BOT.to_string()) - .creator_id(inserted_bot.id) - .community_id(inserted_community.id) - .build(); - + let new_bot_post = PostInsertForm::new( + POST_BY_BOT.to_string(), + inserted_bot.id, + inserted_community.id, + ); let inserted_bot_post = Post::create(pool, &new_bot_post).await?; + let local_user_view = LocalUserView { local_user: inserted_local_user, local_user_vote_display_mode: LocalUserVoteDisplayMode::default(), @@ -949,8 +951,7 @@ mod tests { Some(&data.local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; let expected_post_listing_with_user = expected_post_view(&data, pool).await?; @@ -999,9 +1000,7 @@ mod tests { .await?; let read_post_listing_single_no_person = - PostView::read(pool, data.inserted_post.id, None, false) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + PostView::read(pool, data.inserted_post.id, None, false).await?; let expected_post_listing_no_person = expected_post_view(&data, pool).await?; @@ -1023,6 +1022,65 @@ mod tests { cleanup(data, pool).await } + #[tokio::test] + #[serial] + async fn post_listing_title_only() -> LemmyResult<()> { + let pool = &build_db_pool().await?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // A post which contains the search them 'Post' not in the title (but in the body) + let new_post = PostInsertForm { + language_id: Some(LanguageId(47)), + body: Some("Post".to_string()), + ..PostInsertForm::new( + POST_WITH_ANOTHER_TITLE.to_string(), + data.local_user_view.person.id, + data.inserted_community.id, + ) + }; + + let inserted_post = Post::create(pool, &new_post).await?; + + let read_post_listing_by_title_only = PostQuery { + community_id: Some(data.inserted_community.id), + local_user: None, + search_term: Some("Post".to_string()), + title_only: Some(true), + ..data.default_post_query() + } + .list(&data.site, pool) + .await?; + + let read_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + local_user: None, + search_term: Some("Post".to_string()), + ..data.default_post_query() + } + .list(&data.site, pool) + .await?; + + // Should be 4 posts when we do not search for title only + assert_eq!( + vec![ + POST_WITH_ANOTHER_TITLE, + POST_BY_BOT, + POST, + POST_BY_BLOCKED_PERSON + ], + names(&read_post_listing) + ); + + // Should be 3 posts when we search for title only + assert_eq!( + vec![POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON], + names(&read_post_listing_by_title_only) + ); + Post::delete(pool, inserted_post.id).await?; + cleanup(data, pool).await + } + #[tokio::test] #[serial] async fn post_listing_block_community() -> LemmyResult<()> { @@ -1078,8 +1136,7 @@ mod tests { Some(&data.local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; let mut expected_post_with_upvote = expected_post_view(&data, pool).await?; expected_post_with_upvote.my_vote = Some(1); @@ -1157,6 +1214,36 @@ mod tests { cleanup(data, pool).await } + #[tokio::test] + #[serial] + async fn post_listing_saved_only() -> LemmyResult<()> { + let pool = &build_db_pool().await?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Save only the bot post + // The saved_only should only show the bot post + let post_save_form = PostSavedForm { + post_id: data.inserted_bot_post.id, + person_id: data.local_user_view.person.id, + }; + PostSaved::save(pool, &post_save_form).await?; + + // Read the saved only + let read_saved_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + saved_only: Some(true), + ..data.default_post_query() + } + .list(&data.site, pool) + .await?; + + // This should only include the bot post, not the one you created + assert_eq!(vec![POST_BY_BOT], names(&read_saved_post_listing)); + + cleanup(data, pool).await + } + #[tokio::test] #[serial] async fn creator_info() -> LemmyResult<()> { @@ -1202,21 +1289,18 @@ mod tests { let pool = &mut pool.into(); let data = init_data(pool).await?; - let spanish_id = Language::read_id_from_code(pool, Some("es")) - .await? - .expect("spanish should exist"); + let spanish_id = Language::read_id_from_code(pool, "es").await?; - let french_id = Language::read_id_from_code(pool, Some("fr")) - .await? - .expect("french should exist"); - - let post_spanish = PostInsertForm::builder() - .name(EL_POSTO.to_string()) - .creator_id(data.local_user_view.person.id) - .community_id(data.inserted_community.id) - .language_id(Some(spanish_id)) - .build(); + let french_id = Language::read_id_from_code(pool, "fr").await?; + let post_spanish = PostInsertForm { + language_id: Some(spanish_id), + ..PostInsertForm::new( + EL_POSTO.to_string(), + data.local_user_view.person.id, + data.inserted_community.id, + ) + }; Post::create(pool, &post_spanish).await?; let post_listings_all = data.default_post_query().list(&data.site, pool).await?; @@ -1333,6 +1417,43 @@ mod tests { cleanup(data, pool).await } + #[tokio::test] + #[serial] + async fn post_listings_hidden_community() -> LemmyResult<()> { + let pool = &build_db_pool().await?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + Community::update( + pool, + data.inserted_community.id, + &CommunityUpdateForm { + hidden: Some(true), + ..Default::default() + }, + ) + .await?; + + let posts = PostQuery::default().list(&data.site, pool).await?; + assert!(posts.is_empty()); + + let posts = data.default_post_query().list(&data.site, pool).await?; + assert!(posts.is_empty()); + + // Follow the community + let form = CommunityFollowerForm { + community_id: data.inserted_community.id, + person_id: data.local_user_view.person.id, + pending: false, + }; + CommunityFollower::follow(pool, &form).await?; + + let posts = data.default_post_query().list(&data.site, pool).await?; + assert!(!posts.is_empty()); + + cleanup(data, pool).await + } + #[tokio::test] #[serial] async fn post_listing_instance_block() -> LemmyResult<()> { @@ -1344,21 +1465,22 @@ mod tests { let blocked_instance = Instance::read_or_create(pool, "another_domain.tld".to_string()).await?; - let community_form = CommunityInsertForm::builder() - .name("test_community_4".to_string()) - .title("none".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(blocked_instance.id) - .build(); + let community_form = CommunityInsertForm::new( + blocked_instance.id, + "test_community_4".to_string(), + "none".to_owned(), + "pubkey".to_string(), + ); let inserted_community = Community::create(pool, &community_form).await?; - let post_form = PostInsertForm::builder() - .name(POST_FROM_BLOCKED_INSTANCE.to_string()) - .creator_id(data.inserted_bot.id) - .community_id(inserted_community.id) - .language_id(Some(LanguageId(1))) - .build(); - + let post_form = PostInsertForm { + language_id: Some(LanguageId(1)), + ..PostInsertForm::new( + POST_FROM_BLOCKED_INSTANCE.to_string(), + data.inserted_bot.id, + inserted_community.id, + ) + }; let post_from_blocked_instance = Post::create(pool, &post_form).await?; // no instance block, should return all posts @@ -1401,12 +1523,12 @@ mod tests { let pool = &mut pool.into(); let data = init_data(pool).await?; - let community_form = CommunityInsertForm::builder() - .name("yes".to_string()) - .title("yes".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(data.inserted_instance.id) - .build(); + let community_form = CommunityInsertForm::new( + data.inserted_instance.id, + "yes".to_string(), + "yes".to_owned(), + "pubkey".to_string(), + ); let inserted_community = Community::create(pool, &community_form).await?; let mut inserted_post_ids = vec![]; @@ -1416,23 +1538,25 @@ mod tests { // and featured for comments in 0..10 { for _ in 0..15 { - let post_form = PostInsertForm::builder() - .name("keep Christ in Christmas".to_owned()) - .creator_id(data.local_user_view.person.id) - .community_id(inserted_community.id) - .featured_local(Some((comments % 2) == 0)) - .featured_community(Some((comments % 2) == 0)) - .published(Some(Utc::now() - Duration::from_secs(comments % 3))) - .build(); + let post_form = PostInsertForm { + featured_local: Some((comments % 2) == 0), + featured_community: Some((comments % 2) == 0), + published: Some(Utc::now() - Duration::from_secs(comments % 3)), + ..PostInsertForm::new( + "keep Christ in Christmas".to_owned(), + data.local_user_view.person.id, + inserted_community.id, + ) + }; let inserted_post = Post::create(pool, &post_form).await?; inserted_post_ids.push(inserted_post.id); for _ in 0..comments { - let comment_form = CommentInsertForm::builder() - .creator_id(data.local_user_view.person.id) - .post_id(inserted_post.id) - .content("yes".to_owned()) - .build(); + let comment_form = CommentInsertForm::new( + data.local_user_view.person.id, + inserted_post.id, + "yes".to_owned(), + ); let inserted_comment = Comment::create(pool, &comment_form, None).await?; inserted_comment_ids.push(inserted_comment.id); } @@ -1441,7 +1565,7 @@ mod tests { let options = PostQuery { community_id: Some(inserted_community.id), - sort: Some(SortType::MostComments), + sort: Some(PostSortType::MostComments), limit: Some(10), ..Default::default() }; @@ -1573,7 +1697,7 @@ mod tests { // Make sure it does come back with the show_hidden option let post_listings_show_hidden = PostQuery { - sort: Some(SortType::New), + sort: Some(PostSortType::New), local_user: Some(&data.local_user_view.local_user), show_hidden: Some(true), ..Default::default() @@ -1583,12 +1707,7 @@ mod tests { assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_hidden)); // Make sure that hidden field is true. - assert!( - &post_listings_show_hidden - .first() - .ok_or(LemmyErrorType::CouldntFindPost)? - .hidden - ); + assert!(&post_listings_show_hidden.first().is_some_and(|p| p.hidden)); cleanup(data, pool).await } @@ -1614,7 +1733,7 @@ mod tests { // Make sure it does come back with the show_nsfw option let post_listings_show_nsfw = PostQuery { - sort: Some(SortType::New), + sort: Some(PostSortType::New), show_nsfw: Some(true), local_user: Some(&data.local_user_view.local_user), ..Default::default() @@ -1624,13 +1743,7 @@ mod tests { assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_nsfw)); // Make sure that nsfw field is true. - assert!( - &post_listings_show_nsfw - .first() - .ok_or(LemmyErrorType::CouldntFindPost)? - .post - .nsfw - ); + assert!(&post_listings_show_nsfw.first().is_some_and(|p| p.post.nsfw)); cleanup(data, pool).await } @@ -1653,9 +1766,7 @@ mod tests { &data.inserted_community, &data.inserted_post, ); - let agg = PostAggregates::read(pool, inserted_post.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + let agg = PostAggregates::read(pool, inserted_post.id).await?; Ok(PostView { post: Post { @@ -1682,6 +1793,7 @@ mod tests { featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }, my_vote: None, unread_comments: 0, @@ -1700,7 +1812,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_person.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: data.inserted_instance.id, @@ -1735,7 +1846,6 @@ mod tests { last_refreshed_at: inserted_community.last_refreshed_at, followers_url: inserted_community.followers_url.clone(), inbox_url: inserted_community.inbox_url.clone(), - shared_inbox_url: inserted_community.shared_inbox_url.clone(), moderators_url: inserted_community.moderators_url.clone(), featured_url: inserted_community.featured_url.clone(), visibility: CommunityVisibility::Public, @@ -1799,8 +1909,8 @@ mod tests { .await?; assert_eq!(2, authenticated_query.len()); - let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await?; - assert!(unauthenticated_post.is_none()); + let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await; + assert!(unauthenticated_post.is_err()); let authenticated_post = PostView::read( pool, @@ -1850,8 +1960,7 @@ mod tests { Some(&inserted_banned_from_comm_local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; assert!(post_view.banned_from_community); @@ -1872,8 +1981,7 @@ mod tests { Some(&data.local_user_view.local_user), false, ) - .await? - .ok_or(LemmyErrorType::CouldntFindPost)?; + .await?; assert!(!post_view.banned_from_community); diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/private_message_report_view.rs index f5e70fb3e..56d0d6e7b 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -78,7 +78,7 @@ impl PrivateMessageReportView { pub async fn read( pool: &mut DbPool<'_>, report_id: PrivateMessageReportId, - ) -> Result, Error> { + ) -> Result { queries().read(pool, report_id).await } @@ -111,8 +111,7 @@ impl PrivateMessageReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::private_message_report_view::PrivateMessageReportQuery; @@ -127,32 +126,31 @@ mod tests { traits::{Crud, Reportable}, 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_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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person_1 = PersonInsertForm::test_form(inserted_instance.id, "timmy_mrv"); - let inserted_timmy = Person::create(pool, &new_person_1).await.unwrap(); + let inserted_timmy = Person::create(pool, &new_person_1).await?; let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "jessica_mrv"); - let inserted_jessica = Person::create(pool, &new_person_2).await.unwrap(); + let inserted_jessica = Person::create(pool, &new_person_2).await?; // timmy sends private message to jessica - let pm_form = PrivateMessageInsertForm::builder() - .creator_id(inserted_timmy.id) - .recipient_id(inserted_jessica.id) - .content("something offensive".to_string()) - .build(); - let pm = PrivateMessage::create(pool, &pm_form).await.unwrap(); + let pm_form = PrivateMessageInsertForm::new( + inserted_timmy.id, + inserted_jessica.id, + "something offensive".to_string(), + ); + let pm = PrivateMessage::create(pool, &pm_form).await?; // jessica reports private message let pm_report_form = PrivateMessageReportForm { @@ -161,14 +159,9 @@ mod tests { private_message_id: pm.id, reason: "its offensive".to_string(), }; - let pm_report = PrivateMessageReport::report(pool, &pm_report_form) - .await - .unwrap(); + let pm_report = PrivateMessageReport::report(pool, &pm_report_form).await?; - let reports = PrivateMessageReportQuery::default() - .list(pool) - .await - .unwrap(); + let reports = PrivateMessageReportQuery::default().list(pool).await?; assert_length!(1, reports); assert!(!reports[0].private_message_report.resolved); assert_eq!(inserted_timmy.name, reports[0].private_message_creator.name); @@ -177,28 +170,27 @@ mod tests { assert_eq!(pm.content, reports[0].private_message.content); let new_person_3 = PersonInsertForm::test_form(inserted_instance.id, "admin_mrv"); - let inserted_admin = Person::create(pool, &new_person_3).await.unwrap(); + let inserted_admin = Person::create(pool, &new_person_3).await?; // admin resolves the report (after taking appropriate action) - PrivateMessageReport::resolve(pool, pm_report.id, inserted_admin.id) - .await - .unwrap(); + PrivateMessageReport::resolve(pool, pm_report.id, inserted_admin.id).await?; let reports = PrivateMessageReportQuery { unresolved_only: (false), ..Default::default() } .list(pool) - .await - .unwrap(); + .await?; assert_length!(1, reports); assert!(reports[0].private_message_report.resolved); assert!(reports[0].resolver.is_some()); assert_eq!( - inserted_admin.name, - reports[0].resolver.as_ref().unwrap().name + Some(&inserted_admin.name), + reports[0].resolver.as_ref().map(|r| &r.name) ); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index 79224d86f..0fbc0ee16 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -113,7 +113,7 @@ impl PrivateMessageView { pub async fn read( pool: &mut DbPool<'_>, private_message_id: PrivateMessageId, - ) -> Result, Error> { + ) -> Result { queries().read(pool, private_message_id).await } @@ -173,8 +173,7 @@ impl PrivateMessageQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView}; @@ -205,57 +204,35 @@ mod tests { async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { let message_content = String::new(); - let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) - .await - .unwrap(); + let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_rav"); - let timmy = Person::create(pool, &timmy_form).await.unwrap(); + let timmy = Person::create(pool, &timmy_form).await?; let sara_form = PersonInsertForm::test_form(instance.id, "sara_rav"); - let sara = Person::create(pool, &sara_form).await.unwrap(); + let sara = Person::create(pool, &sara_form).await?; let jess_form = PersonInsertForm::test_form(instance.id, "jess_rav"); - let jess = Person::create(pool, &jess_form).await.unwrap(); + let jess = Person::create(pool, &jess_form).await?; - let sara_timmy_message_form = PrivateMessageInsertForm::builder() - .creator_id(sara.id) - .recipient_id(timmy.id) - .content(message_content.clone()) - .build(); - PrivateMessage::create(pool, &sara_timmy_message_form) - .await - .unwrap(); + let sara_timmy_message_form = + PrivateMessageInsertForm::new(sara.id, timmy.id, message_content.clone()); + PrivateMessage::create(pool, &sara_timmy_message_form).await?; - let sara_jess_message_form = PrivateMessageInsertForm::builder() - .creator_id(sara.id) - .recipient_id(jess.id) - .content(message_content.clone()) - .build(); - PrivateMessage::create(pool, &sara_jess_message_form) - .await - .unwrap(); + let sara_jess_message_form = + PrivateMessageInsertForm::new(sara.id, jess.id, message_content.clone()); + PrivateMessage::create(pool, &sara_jess_message_form).await?; - let timmy_sara_message_form = PrivateMessageInsertForm::builder() - .creator_id(timmy.id) - .recipient_id(sara.id) - .content(message_content.clone()) - .build(); - PrivateMessage::create(pool, &timmy_sara_message_form) - .await - .unwrap(); + let timmy_sara_message_form = + PrivateMessageInsertForm::new(timmy.id, sara.id, message_content.clone()); + PrivateMessage::create(pool, &timmy_sara_message_form).await?; - let jess_timmy_message_form = PrivateMessageInsertForm::builder() - .creator_id(jess.id) - .recipient_id(timmy.id) - .content(message_content.clone()) - .build(); - PrivateMessage::create(pool, &jess_timmy_message_form) - .await - .unwrap(); + let jess_timmy_message_form = + PrivateMessageInsertForm::new(jess.id, timmy.id, message_content.clone()); + PrivateMessage::create(pool, &jess_timmy_message_form).await?; Ok(Data { instance, @@ -267,7 +244,7 @@ mod tests { async fn cleanup(instance_id: InstanceId, pool: &mut DbPool<'_>) -> LemmyResult<()> { // This also deletes all persons and private messages thanks to sql `on delete cascade` - Instance::delete(pool, instance_id).await.unwrap(); + Instance::delete(pool, instance_id).await?; Ok(()) } @@ -289,8 +266,7 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(3, &timmy_messages); assert_eq!(timmy_messages[0].creator.id, jess.id); @@ -306,8 +282,7 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(2, &timmy_unread_messages); assert_eq!(timmy_unread_messages[0].creator.id, jess.id); @@ -321,8 +296,7 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(2, &timmy_sara_messages); assert_eq!(timmy_sara_messages[0].creator.id, timmy.id); @@ -336,8 +310,7 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(1, &timmy_sara_unread_messages); assert_eq!(timmy_sara_unread_messages[0].creator.id, sara.id); @@ -364,9 +337,7 @@ mod tests { target_id: sara.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: timmy.id, @@ -381,14 +352,11 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(1, &timmy_messages); - let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id) - .await - .unwrap(); + let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id).await?; assert_eq!(timmy_unread_messages, 1); cleanup(instance.id, pool).await @@ -411,9 +379,7 @@ mod tests { instance_id: sara.instance_id, }; - let inserted_instance_block = InstanceBlock::block(pool, &timmy_blocks_instance_form) - .await - .unwrap(); + let inserted_instance_block = InstanceBlock::block(pool, &timmy_blocks_instance_form).await?; let expected_instance_block = InstanceBlock { person_id: timmy.id, @@ -428,14 +394,11 @@ mod tests { ..Default::default() } .list(pool, timmy.id) - .await - .unwrap(); + .await?; assert_length!(0, &timmy_messages); - let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id) - .await - .unwrap(); + let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id).await?; assert_eq!(timmy_unread_messages, 0); cleanup(instance.id, pool).await } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 6f806be13..a0a40789b 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -81,17 +81,11 @@ fn queries<'a>() -> Queries< } impl RegistrationApplicationView { - pub async fn read( - pool: &mut DbPool<'_>, - id: RegistrationApplicationId, - ) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, id: RegistrationApplicationId) -> Result { queries().read(pool, ReadBy::Id(id)).await } - pub async fn read_by_person( - pool: &mut DbPool<'_>, - person_id: PersonId, - ) -> Result, Error> { + pub async fn read_by_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { queries().read(pool, ReadBy::Person(person_id)).await } /// Returns the current unread registration_application count @@ -141,8 +135,6 @@ impl RegistrationApplicationQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::registration_application_view::{ @@ -163,38 +155,34 @@ 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_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 - .unwrap(); + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_rav"); - let inserted_timmy_person = Person::create(pool, &timmy_person_form).await.unwrap(); + let inserted_timmy_person = Person::create(pool, &timmy_person_form).await?; let timmy_local_user_form = LocalUserInsertForm::test_form_admin(inserted_timmy_person.id); - 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::test_form(inserted_instance.id, "sara_rav"); - let inserted_sara_person = Person::create(pool, &sara_person_form).await.unwrap(); + let inserted_sara_person = Person::create(pool, &sara_person_form).await?; let sara_local_user_form = LocalUserInsertForm::test_form(inserted_sara_person.id); - let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![]) - .await - .unwrap(); + let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![]).await?; // Sara creates an application let sara_app_form = RegistrationApplicationInsertForm { @@ -202,24 +190,17 @@ mod tests { answer: "LET ME IIIIINN".to_string(), }; - let sara_app = RegistrationApplication::create(pool, &sara_app_form) - .await - .unwrap(); + let sara_app = RegistrationApplication::create(pool, &sara_app_form).await?; - let read_sara_app_view = RegistrationApplicationView::read(pool, sara_app.id) - .await - .unwrap() - .unwrap(); + let read_sara_app_view = RegistrationApplicationView::read(pool, sara_app.id).await?; let jess_person_form = PersonInsertForm::test_form(inserted_instance.id, "jess_rav"); - let inserted_jess_person = Person::create(pool, &jess_person_form).await.unwrap(); + let inserted_jess_person = Person::create(pool, &jess_person_form).await?; let jess_local_user_form = LocalUserInsertForm::test_form(inserted_jess_person.id); - let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![]) - .await - .unwrap(); + let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![]).await?; // Sara creates an application let jess_app_form = RegistrationApplicationInsertForm { @@ -227,14 +208,9 @@ mod tests { answer: "LET ME IIIIINN".to_string(), }; - let jess_app = RegistrationApplication::create(pool, &jess_app_form) - .await - .unwrap(); + let jess_app = RegistrationApplication::create(pool, &jess_app_form).await?; - let read_jess_app_view = RegistrationApplicationView::read(pool, jess_app.id) - .await - .unwrap() - .unwrap(); + let read_jess_app_view = RegistrationApplicationView::read(pool, jess_app.id).await?; let mut expected_sara_app_view = RegistrationApplicationView { registration_application: sara_app.clone(), @@ -243,10 +219,10 @@ mod tests { person_id: inserted_sara_local_user.person_id, email: inserted_sara_local_user.email, show_nsfw: inserted_sara_local_user.show_nsfw, - auto_expand: inserted_sara_local_user.auto_expand, blur_nsfw: inserted_sara_local_user.blur_nsfw, theme: inserted_sara_local_user.theme, - default_sort_type: inserted_sara_local_user.default_sort_type, + default_post_sort_type: inserted_sara_local_user.default_post_sort_type, + default_comment_sort_type: inserted_sara_local_user.default_comment_sort_type, default_listing_type: inserted_sara_local_user.default_listing_type, interface_language: inserted_sara_local_user.interface_language, show_avatars: inserted_sara_local_user.show_avatars, @@ -282,7 +258,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_sara_person.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, instance_id: inserted_instance.id, private_key: inserted_sara_person.private_key, @@ -300,8 +275,7 @@ mod tests { ..Default::default() } .list(pool) - .await - .unwrap(); + .await?; assert_eq!( apps, @@ -309,9 +283,7 @@ mod tests { ); // Make sure the counts are correct - let unread_count = RegistrationApplicationView::get_unread_count(pool, false) - .await - .unwrap(); + let unread_count = RegistrationApplicationView::get_unread_count(pool, false).await?; assert_eq!(unread_count, 2); // Approve the application @@ -320,9 +292,7 @@ mod tests { deny_reason: None, }; - RegistrationApplication::update(pool, sara_app.id, &approve_form) - .await - .unwrap(); + RegistrationApplication::update(pool, sara_app.id, &approve_form).await?; // Update the local_user row let approve_local_user_form = LocalUserUpdateForm { @@ -330,14 +300,10 @@ mod tests { ..Default::default() }; - LocalUser::update(pool, inserted_sara_local_user.id, &approve_local_user_form) - .await - .unwrap(); + LocalUser::update(pool, inserted_sara_local_user.id, &approve_local_user_form).await?; - let read_sara_app_view_after_approve = RegistrationApplicationView::read(pool, sara_app.id) - .await - .unwrap() - .unwrap(); + let read_sara_app_view_after_approve = + RegistrationApplicationView::read(pool, sara_app.id).await?; // Make sure the columns changed expected_sara_app_view @@ -361,7 +327,6 @@ mod tests { banner: None, updated: None, inbox_url: inserted_timmy_person.inbox_url.clone(), - shared_inbox_url: None, matrix_user_id: None, instance_id: inserted_instance.id, private_key: inserted_timmy_person.private_key, @@ -377,28 +342,23 @@ mod tests { ..Default::default() } .list(pool) - .await - .unwrap(); + .await?; assert_eq!(apps_after_resolve, vec![read_jess_app_view]); // Make sure the counts are correct - let unread_count_after_approve = RegistrationApplicationView::get_unread_count(pool, false) - .await - .unwrap(); + let unread_count_after_approve = + RegistrationApplicationView::get_unread_count(pool, false).await?; assert_eq!(unread_count_after_approve, 1); // Make sure the not undenied_only has all the apps - let all_apps = RegistrationApplicationQuery::default() - .list(pool) - .await - .unwrap(); + let all_apps = RegistrationApplicationQuery::default().list(pool).await?; assert_eq!(all_apps.len(), 2); - Person::delete(pool, inserted_timmy_person.id) - .await - .unwrap(); - Person::delete(pool, inserted_sara_person.id).await.unwrap(); - Person::delete(pool, inserted_jess_person.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Person::delete(pool, inserted_timmy_person.id).await?; + Person::delete(pool, inserted_sara_person.id).await?; + Person::delete(pool, inserted_jess_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_views/src/site_view.rs b/crates/db_views/src/site_view.rs index 8f0722318..6014ad964 100644 --- a/crates/db_views/src/site_view.rs +++ b/crates/db_views/src/site_view.rs @@ -1,28 +1,32 @@ use crate::structs::SiteView; -use diesel::{result::Error, ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl}; +use diesel::{ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ schema::{local_site, local_site_rate_limit, site, site_aggregates}, utils::{get_conn, DbPool}, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl SiteView { - pub async fn read_local(pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn read_local(pool: &mut DbPool<'_>) -> LemmyResult { let conn = &mut get_conn(pool).await?; - site::table - .inner_join(local_site::table) - .inner_join( - local_site_rate_limit::table.on(local_site::id.eq(local_site_rate_limit::local_site_id)), - ) - .inner_join(site_aggregates::table) - .select(( - site::all_columns, - local_site::all_columns, - local_site_rate_limit::all_columns, - site_aggregates::all_columns, - )) - .first(conn) - .await - .optional() + Ok( + site::table + .inner_join(local_site::table) + .inner_join( + local_site_rate_limit::table.on(local_site::id.eq(local_site_rate_limit::local_site_id)), + ) + .inner_join(site_aggregates::table) + .select(( + site::all_columns, + local_site::all_columns, + local_site_rate_limit::all_columns, + site_aggregates::all_columns, + )) + .first(conn) + .await + .optional()? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?, + ) } } diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 5daa072c3..0fd64deca 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -10,7 +10,7 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommentId, PostId}, - schema::{comment_like, community_person_ban, person, post, post_like}, + schema::{comment, comment_like, community_person_ban, person, post, post_like}, utils::{get_conn, limit_and_offset, DbPool}, }; @@ -59,7 +59,8 @@ impl VoteView { comment_like::table .inner_join(person::table) - .inner_join(post::table) + .inner_join(comment::table) + .inner_join(post::table.on(comment::post_id.eq(post::id))) // Join to community_person_ban to get creator_banned_from_community .left_join( community_person_ban::table.on( @@ -83,8 +84,6 @@ impl VoteView { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::structs::VoteView; @@ -99,51 +98,47 @@ mod tests { traits::{Bannable, Crud, Likeable}, 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 post_and_comment_vote_views() { + async fn post_and_comment_vote_views() -> 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "timmy_vv"); - let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); + let inserted_timmy = Person::create(pool, &new_person).await?; let new_person_2 = PersonInsertForm::test_form(inserted_instance.id, "sara_vv"); - let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); + let inserted_sara = Person::create(pool, &new_person_2).await?; - let new_community = CommunityInsertForm::builder() - .name("test community vv".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community vv".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let inserted_community = Community::create(pool, &new_community).await?; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let new_post = PostInsertForm::new( + "A test post vv".into(), + inserted_timmy.id, + inserted_community.id, + ); + let inserted_post = Post::create(pool, &new_post).await?; - let new_post = PostInsertForm::builder() - .name("A test post vv".into()) - .creator_id(inserted_timmy.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 vv".into()) - .creator_id(inserted_timmy.id) - .post_id(inserted_post.id) - .build(); - - let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); + let comment_form = CommentInsertForm::new( + inserted_timmy.id, + inserted_post.id, + "A test comment vv".into(), + ); + let inserted_comment = Comment::create(pool, &comment_form, None).await?; // Timmy upvotes his own post let timmy_post_vote_form = PostLikeForm { @@ -151,7 +146,7 @@ mod tests { person_id: inserted_timmy.id, score: 1, }; - PostLike::like(pool, &timmy_post_vote_form).await.unwrap(); + PostLike::like(pool, &timmy_post_vote_form).await?; // Sara downvotes timmy's post let sara_post_vote_form = PostLikeForm { @@ -159,7 +154,7 @@ mod tests { person_id: inserted_sara.id, score: -1, }; - PostLike::like(pool, &sara_post_vote_form).await.unwrap(); + PostLike::like(pool, &sara_post_vote_form).await?; let expected_post_vote_views = [ VoteView { @@ -174,32 +169,24 @@ mod tests { }, ]; - let read_post_vote_views = VoteView::list_for_post(pool, inserted_post.id, None, None) - .await - .unwrap(); + let read_post_vote_views = VoteView::list_for_post(pool, inserted_post.id, None, None).await?; assert_eq!(read_post_vote_views, expected_post_vote_views); // Timothy votes down his own comment let timmy_comment_vote_form = CommentLikeForm { - post_id: inserted_post.id, comment_id: inserted_comment.id, person_id: inserted_timmy.id, score: -1, }; - CommentLike::like(pool, &timmy_comment_vote_form) - .await - .unwrap(); + CommentLike::like(pool, &timmy_comment_vote_form).await?; // Sara upvotes timmy's comment let sara_comment_vote_form = CommentLikeForm { - post_id: inserted_post.id, comment_id: inserted_comment.id, person_id: inserted_sara.id, score: 1, }; - CommentLike::like(pool, &sara_comment_vote_form) - .await - .unwrap(); + CommentLike::like(pool, &sara_comment_vote_form).await?; let expected_comment_vote_views = [ VoteView { @@ -214,9 +201,8 @@ mod tests { }, ]; - let read_comment_vote_views = VoteView::list_for_comment(pool, inserted_comment.id, None, None) - .await - .unwrap(); + let read_comment_vote_views = + VoteView::list_for_comment(pool, inserted_comment.id, None, None).await?; assert_eq!(read_comment_vote_views, expected_comment_vote_views); // Ban timmy from that community @@ -225,36 +211,26 @@ mod tests { person_id: inserted_timmy.id, expires: None, }; - CommunityPersonBan::ban(pool, &ban_timmy_form) - .await - .unwrap(); + CommunityPersonBan::ban(pool, &ban_timmy_form).await?; // Make sure creator_banned_from_community is true let read_comment_vote_views_after_ban = - VoteView::list_for_comment(pool, inserted_comment.id, None, None) - .await - .unwrap(); + VoteView::list_for_comment(pool, inserted_comment.id, None, None).await?; - assert!( - read_comment_vote_views_after_ban - .first() - .unwrap() - .creator_banned_from_community - ); + assert!(read_comment_vote_views_after_ban + .first() + .is_some_and(|c| c.creator_banned_from_community)); let read_post_vote_views_after_ban = - VoteView::list_for_post(pool, inserted_post.id, None, None) - .await - .unwrap(); + VoteView::list_for_post(pool, inserted_post.id, None, None).await?; - assert!( - read_post_vote_views_after_ban - .get(1) - .unwrap() - .creator_banned_from_community - ); + assert!(read_post_vote_views_after_ban + .get(1) + .is_some_and(|p| p.creator_banned_from_community)); // Cleanup - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Instance::delete(pool, inserted_instance.id).await?; + + Ok(()) } } diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index af139b8b2..c29b7d66d 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -15,7 +15,13 @@ doctest = false workspace = true [features] -full = ["lemmy_db_schema/full", "diesel", "diesel-async", "ts-rs"] +full = [ + "lemmy_db_schema/full", + "lemmy_utils/full", + "diesel", + "diesel-async", + "ts-rs", +] [dependencies] lemmy_db_schema = { workspace = true } @@ -33,6 +39,7 @@ serde_with = { workspace = true } ts-rs = { workspace = true, optional = true } chrono.workspace = true strum = { workspace = true } +lemmy_utils = { workspace = true, optional = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index b1d95e719..1b657866a 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -242,7 +242,7 @@ impl CommentReplyView { pool: &mut DbPool<'_>, comment_reply_id: CommentReplyId, my_person_id: Option, - ) -> Result, Error> { + ) -> Result { queries().read(pool, (comment_reply_id, my_person_id)).await } @@ -303,7 +303,6 @@ impl CommentReplyQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{comment_reply_view::CommentReplyQuery, structs::CommentReplyView}; @@ -322,7 +321,7 @@ mod tests { utils::build_db_pool_for_tests, }; use lemmy_db_views::structs::LocalUserView; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -348,29 +347,23 @@ mod tests { 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 new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community lake".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); 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 new_post = PostInsertForm::new( + "A test post".into(), + inserted_terry.id, + inserted_community.id, + ); 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 comment_form = + CommentInsertForm::new(inserted_terry.id, inserted_post.id, "A test comment".into()); let inserted_comment = Comment::create(pool, &comment_form, None).await?; let comment_reply_form = CommentReplyInsertForm { @@ -389,9 +382,7 @@ mod tests { published: inserted_reply.published, }; - let read_reply = CommentReply::read(pool, inserted_reply.id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let read_reply = CommentReply::read(pool, inserted_reply.id).await?; let comment_reply_update_form = CommentReplyUpdateForm { read: Some(false) }; let updated_reply = @@ -446,9 +437,7 @@ mod tests { &recipient_local_user_update_form, ) .await?; - let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?; let unread_replies_after_hide_bots = CommentReplyView::get_unread_replies(pool, &recipient_local_user_view.local_user).await?; diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index 7b942e043..92889d12d 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -10,7 +10,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, schema::{community, community_follower, person}, - utils::{functions::coalesce, get_conn, DbPool}, + utils::{get_conn, DbPool}, }; impl CommunityFollowerView { @@ -37,10 +37,7 @@ impl CommunityFollowerView { // local-person+remote-community or remote-person+local-community .filter(not(person::local)) .filter(community_follower::published.gt(published_since.naive_utc())) - .select(( - community::id, - coalesce(person::shared_inbox_url, person::inbox_url), - )) + .select((community::id, person::inbox_url)) .distinct() // only need each community_id, inbox combination once .load::<(CommunityId, DbUrl)>(conn) .await @@ -54,7 +51,7 @@ impl CommunityFollowerView { .filter(community_follower::community_id.eq(community_id)) .filter(not(person::local)) .inner_join(person::table) - .select(coalesce(person::shared_inbox_url, person::inbox_url)) + .select(person::inbox_url) .distinct() .load::(conn) .await?; diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index f2a59fd9f..ebcdcbd25 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -8,13 +8,14 @@ use lemmy_db_schema::{ source::local_user::LocalUser, utils::{get_conn, DbPool}, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityModeratorView { - pub async fn is_community_moderator( + pub async fn check_is_community_moderator( pool: &mut DbPool<'_>, find_community_id: CommunityId, find_person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { use lemmy_db_schema::schema::community_moderator::dsl::{ community_id, community_moderator, @@ -27,20 +28,24 @@ impl CommunityModeratorView { .filter(person_id.eq(find_person_id)), )) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotAModerator.into()) } pub(crate) async fn is_community_moderator_of_any( pool: &mut DbPool<'_>, find_person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id}; let conn = &mut get_conn(pool).await?; select(exists( community_moderator.filter(person_id.eq(find_person_id)), )) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotAModerator.into()) } pub async fn for_community( diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index 712bb2d3a..5543222f3 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -1,25 +1,33 @@ use crate::structs::CommunityPersonBanView; -use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; +use diesel::{ + dsl::{exists, not}, + select, + ExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::community_person_ban, utils::{get_conn, DbPool}, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityPersonBanView { - pub async fn get( + pub async fn check( pool: &mut DbPool<'_>, from_person_id: PersonId, from_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( community_person_ban::table .filter(community_person_ban::community_id.eq(from_community_id)) .filter(community_person_ban::person_id.eq(from_person_id)), - )) + ))) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into()) } } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 0e731878a..9ff6fadce 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -1,4 +1,4 @@ -use crate::structs::{CommunityModeratorView, CommunityView, PersonView}; +use crate::structs::{CommunityModeratorView, CommunitySortType, CommunityView, PersonView}; use diesel::{ pg::Pg, result::Error, @@ -22,10 +22,20 @@ use lemmy_db_schema::{ instance_block, }, source::{community::CommunityFollower, local_user::LocalUser, site::Site}, - utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{ + functions::lower, + fuzzy_search, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, ListingType, - SortType, + PostSortType, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; fn queries<'a>() -> Queries< impl ReadFn<'a, CommunityView, (CommunityId, Option<&'a LocalUser>, bool)>, @@ -102,7 +112,7 @@ fn queries<'a>() -> Queries< }; let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move { - use SortType::*; + use CommunitySortType::*; // The left join below will return None in this case let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); @@ -111,9 +121,14 @@ fn queries<'a>() -> Queries< if let Some(search_term) = options.search_term { let searcher = fuzzy_search(&search_term); - query = query - .filter(community::name.ilike(searcher.clone())) - .or_filter(community::title.ilike(searcher)) + let name_filter = community::name.ilike(searcher.clone()); + let title_filter = community::title.ilike(searcher.clone()); + let description_filter = community::description.ilike(searcher.clone()); + query = if options.title_only.unwrap_or_default() { + query.filter(name_filter.or(title_filter)) + } else { + query.filter(name_filter.or(title_filter.or(description_filter))) + } } // Hide deleted and removed for non-admins or mods @@ -142,6 +157,8 @@ fn queries<'a>() -> Queries< } TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()), TopWeek => query = query.order_by(community_aggregates::users_active_week.desc()), + NameAsc => query = query.order_by(lower(community::name).asc()), + NameDesc => query = query.order_by(lower(community::name).desc()), }; if let Some(listing_type) = options.listing_type { @@ -179,41 +196,71 @@ impl CommunityView { community_id: CommunityId, my_local_user: Option<&'a LocalUser>, is_mod_or_admin: bool, - ) -> Result, Error> { + ) -> Result { queries() .read(pool, (community_id, my_local_user, is_mod_or_admin)) .await } - pub async fn is_mod_or_admin( + pub async fn check_is_mod_or_admin( pool: &mut DbPool<'_>, person_id: PersonId, community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let is_mod = - CommunityModeratorView::is_community_moderator(pool, community_id, person_id).await?; - if is_mod { - Ok(true) - } else if let Ok(Some(person_view)) = PersonView::read(pool, person_id).await { - Ok(person_view.is_admin) + CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await; + if is_mod.is_ok() + || PersonView::read(pool, person_id) + .await + .is_ok_and(|t| t.is_admin) + { + Ok(()) } else { - Ok(false) + Err(LemmyErrorType::NotAModOrAdmin)? } } /// Checks if a person is an admin, or moderator of any community. - pub async fn is_mod_of_any_or_admin( + pub async fn check_is_mod_of_any_or_admin( pool: &mut DbPool<'_>, person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { let is_mod_of_any = - CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await?; - if is_mod_of_any { - Ok(true) - } else if let Ok(Some(person_view)) = PersonView::read(pool, person_id).await { - Ok(person_view.is_admin) + CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await; + if is_mod_of_any.is_ok() + || PersonView::read(pool, person_id) + .await + .is_ok_and(|t| t.is_admin) + { + Ok(()) } else { - Ok(false) + Err(LemmyErrorType::NotAModOrAdmin)? + } + } +} + +impl From for CommunitySortType { + fn from(value: PostSortType) -> Self { + match value { + PostSortType::Active => Self::Active, + PostSortType::Hot => Self::Hot, + PostSortType::New => Self::New, + PostSortType::Old => Self::Old, + PostSortType::TopDay => Self::TopDay, + PostSortType::TopWeek => Self::TopWeek, + PostSortType::TopMonth => Self::TopMonth, + PostSortType::TopYear => Self::TopYear, + PostSortType::TopAll => Self::TopAll, + PostSortType::MostComments => Self::MostComments, + PostSortType::NewComments => Self::NewComments, + PostSortType::TopHour => Self::TopHour, + PostSortType::TopSixHour => Self::TopSixHour, + PostSortType::TopTwelveHour => Self::TopTwelveHour, + PostSortType::TopThreeMonths => Self::TopThreeMonths, + PostSortType::TopSixMonths => Self::TopSixMonths, + PostSortType::TopNineMonths => Self::TopNineMonths, + PostSortType::Controversial => Self::Controversial, + PostSortType::Scaled => Self::Scaled, } } } @@ -221,9 +268,10 @@ impl CommunityView { #[derive(Default)] pub struct CommunityQuery<'a> { pub listing_type: Option, - pub sort: Option, + pub sort: Option, pub local_user: Option<&'a LocalUser>, pub search_term: Option, + pub title_only: Option, pub is_mod_or_admin: bool, pub show_nsfw: bool, pub page: Option, @@ -237,11 +285,12 @@ impl<'a> CommunityQuery<'a> { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { - use crate::{community_view::CommunityQuery, structs::CommunityView}; + use crate::{ + community_view::CommunityQuery, + structs::{CommunitySortType, CommunityView}, + }; use lemmy_db_schema::{ source::{ community::{Community, CommunityInsertForm, CommunityUpdateForm}, @@ -254,42 +303,63 @@ mod tests { utils::{build_db_pool_for_tests, DbPool}, CommunityVisibility, }; + use lemmy_utils::error::LemmyResult; use serial_test::serial; use url::Url; struct Data { inserted_instance: Instance, local_user: LocalUser, - inserted_community: Community, + inserted_communities: [Community; 3], site: Site, } - 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 person_name = "tegan".to_string(); let new_person = PersonInsertForm::test_form(inserted_instance.id, &person_name); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let local_user_form = LocalUserInsertForm::test_form(inserted_person.id); - let local_user = LocalUser::create(pool, &local_user_form, vec![]) - .await - .unwrap(); + let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; - let new_community = CommunityInsertForm::builder() - .name("test_community_3".to_string()) - .title("nada".to_owned()) - .public_key("pubkey".to_string()) - .instance_id(inserted_instance.id) - .build(); + let inserted_communities = [ + Community::create( + pool, + &CommunityInsertForm::new( + inserted_instance.id, + "test_community_1".to_string(), + "nada1".to_owned(), + "pubkey".to_string(), + ), + ) + .await?, + Community::create( + pool, + &CommunityInsertForm::new( + inserted_instance.id, + "test_community_2".to_string(), + "nada2".to_owned(), + "pubkey".to_string(), + ), + ) + .await?, + Community::create( + pool, + &CommunityInsertForm::new( + inserted_instance.id, + "test_community_3".to_string(), + "nada3".to_owned(), + "pubkey".to_string(), + ), + ) + .await?, + ]; - let inserted_community = Community::create(pool, &new_community).await.unwrap(); - - let url = Url::parse("http://example.com").unwrap(); + let url = Url::parse("http://example.com")?; let site = Site { id: Default::default(), name: String::new(), @@ -308,76 +378,102 @@ mod tests { content_warning: None, }; - Data { + Ok(Data { inserted_instance, local_user, - inserted_community, + inserted_communities, site, - } + }) } - async fn cleanup(data: Data, pool: &mut DbPool<'_>) { - Community::delete(pool, data.inserted_community.id) - .await - .unwrap(); - Person::delete(pool, data.local_user.person_id) - .await - .unwrap(); - Instance::delete(pool, data.inserted_instance.id) - .await - .unwrap(); + async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> { + for Community { id, .. } in data.inserted_communities { + Community::delete(pool, id).await?; + } + Person::delete(pool, data.local_user.person_id).await?; + Instance::delete(pool, data.inserted_instance.id).await?; + + Ok(()) } #[tokio::test] #[serial] - async fn local_only_community() { + async fn local_only_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?; Community::update( pool, - data.inserted_community.id, + data.inserted_communities[0].id, &CommunityUpdateForm { visibility: Some(CommunityVisibility::LocalOnly), ..Default::default() }, ) - .await - .unwrap(); + .await?; let unauthenticated_query = CommunityQuery { ..Default::default() } .list(&data.site, pool) - .await - .unwrap(); - assert_eq!(0, unauthenticated_query.len()); + .await?; + assert_eq!( + data.inserted_communities.len() - 1, + unauthenticated_query.len() + ); let authenticated_query = CommunityQuery { local_user: Some(&data.local_user), ..Default::default() } .list(&data.site, pool) - .await - .unwrap(); - assert_eq!(1, authenticated_query.len()); + .await?; + assert_eq!(data.inserted_communities.len(), authenticated_query.len()); let unauthenticated_community = - CommunityView::read(pool, data.inserted_community.id, None, false) - .await - .unwrap(); - assert!(unauthenticated_community.is_none()); + CommunityView::read(pool, data.inserted_communities[0].id, None, false).await; + assert!(unauthenticated_community.is_err()); let authenticated_community = CommunityView::read( pool, - data.inserted_community.id, + data.inserted_communities[0].id, Some(&data.local_user), false, ) .await; assert!(authenticated_community.is_ok()); - cleanup(data, pool).await; + cleanup(data, pool).await + } + + #[tokio::test] + #[serial] + async fn community_sort_name() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + let query = CommunityQuery { + sort: Some(CommunitySortType::NameAsc), + ..Default::default() + }; + let communities = query.list(&data.site, pool).await?; + for (i, c) in communities.iter().enumerate().skip(1) { + let prev = communities.get(i - 1).expect("No previous community?"); + assert!(c.community.title.cmp(&prev.community.title).is_ge()); + } + + let query = CommunityQuery { + sort: Some(CommunitySortType::NameDesc), + ..Default::default() + }; + let communities = query.list(&data.site, pool).await?; + for (i, c) in communities.iter().enumerate().skip(1) { + let prev = communities.get(i - 1).expect("No previous community?"); + assert!(c.community.title.cmp(&prev.community.title).is_le()); + } + + cleanup(data, pool).await } } diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index d6fd7363d..2478c0183 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -241,7 +241,7 @@ impl PersonMentionView { pool: &mut DbPool<'_>, person_mention_id: PersonMentionId, my_person_id: Option, - ) -> Result, Error> { + ) -> Result { queries() .read(pool, (person_mention_id, my_person_id)) .await @@ -303,7 +303,6 @@ impl PersonMentionQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView}; @@ -322,7 +321,7 @@ mod tests { utils::build_db_pool_for_tests, }; use lemmy_db_views::structs::LocalUserView; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -346,29 +345,26 @@ mod tests { 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 new_community = CommunityInsertForm::new( + inserted_instance.id, + "test community lake".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); 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 new_post = PostInsertForm::new( + "A test post".into(), + inserted_person.id, + inserted_community.id, + ); 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 comment_form = CommentInsertForm::new( + inserted_person.id, + inserted_post.id, + "A test comment".into(), + ); let inserted_comment = Comment::create(pool, &comment_form, None).await?; let person_mention_form = PersonMentionInsertForm { @@ -387,9 +383,7 @@ mod tests { published: inserted_mention.published, }; - let read_mention = PersonMention::read(pool, inserted_mention.id) - .await? - .ok_or(LemmyErrorType::CouldntFindComment)?; + let read_mention = PersonMention::read(pool, inserted_mention.id).await?; let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) }; let updated_mention = @@ -445,9 +439,7 @@ mod tests { &recipient_local_user_update_form, ) .await?; - let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?; let unread_mentions_after_hide_bots = PersonMentionView::get_unread_mentions(pool, &recipient_local_user_view.local_user).await?; diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 7a2edfb44..724a700ad 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -24,7 +24,7 @@ use lemmy_db_schema::{ ReadFn, }, ListingType, - SortType, + PostSortType, }; use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; @@ -46,12 +46,13 @@ enum PersonSortType { PostCount, } -fn post_to_person_sort_type(sort: SortType) -> PersonSortType { +fn post_to_person_sort_type(sort: PostSortType) -> PersonSortType { + use PostSortType::*; match sort { - SortType::Active | SortType::Hot | SortType::Controversial => PersonSortType::CommentScore, - SortType::New | SortType::NewComments => PersonSortType::New, - SortType::MostComments => PersonSortType::MostComments, - SortType::Old => PersonSortType::Old, + Active | Hot | Controversial => PersonSortType::CommentScore, + New | NewComments => PersonSortType::New, + MostComments => PersonSortType::MostComments, + Old => PersonSortType::Old, _ => PersonSortType::CommentScore, } } @@ -134,7 +135,7 @@ fn queries<'a>( } impl PersonView { - pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { queries().read(pool, person_id).await } @@ -149,7 +150,7 @@ impl PersonView { #[derive(Default)] pub struct PersonQuery { - pub sort: Option, + pub sort: Option, pub search_term: Option, pub listing_type: Option, pub page: Option, @@ -163,7 +164,7 @@ impl PersonQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; @@ -177,7 +178,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -242,11 +243,11 @@ mod tests { ) .await?; - let read = PersonView::read(pool, data.alice.id).await?; - assert!(read.is_none()); + let read = PersonView::read(pool, data.alice.id).await; + assert!(read.is_err()); let list = PersonQuery { - sort: Some(SortType::New), + sort: Some(PostSortType::New), ..Default::default() } .list(pool) @@ -302,16 +303,10 @@ mod tests { assert_length!(1, list); assert_eq!(list[0].person.id, data.alice.id); - let is_admin = PersonView::read(pool, data.alice.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? - .is_admin; + let is_admin = PersonView::read(pool, data.alice.id).await?.is_admin; assert!(is_admin); - let is_admin = PersonView::read(pool, data.bob.id) - .await? - .ok_or(LemmyErrorType::CouldntFindPerson)? - .is_admin; + let is_admin = PersonView::read(pool, data.bob.id).await?.is_admin; assert!(!is_admin); cleanup(data, pool).await diff --git a/crates/db_views_actor/src/structs.rs b/crates/db_views_actor/src/structs.rs index 2992d575d..ecf9ba11d 100644 --- a/crates/db_views_actor/src/structs.rs +++ b/crates/db_views_actor/src/structs.rs @@ -59,6 +59,35 @@ pub struct CommunityView { pub banned_from_community: bool, } +/// The community sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub enum CommunitySortType { + #[default] + Active, + Hot, + New, + Old, + TopDay, + TopWeek, + TopMonth, + TopYear, + TopAll, + MostComments, + NewComments, + TopHour, + TopSixHour, + TopTwelveHour, + TopThreeMonths, + TopSixMonths, + TopNineMonths, + Controversial, + Scaled, + NameAsc, + NameDesc, +} + #[skip_serializing_none] #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(TS, Queryable))] diff --git a/crates/federate/src/inboxes.rs b/crates/federate/src/inboxes.rs index cda4da39b..1649e019f 100644 --- a/crates/federate/src/inboxes.rs +++ b/crates/federate/src/inboxes.rs @@ -222,14 +222,14 @@ impl CommunityInboxCollector { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; use lemmy_db_schema::{ newtypes::{ActivityId, CommunityId, InstanceId, SiteId}, source::activity::{ActorType, SentActivity}, }; + use lemmy_utils::error::LemmyResult; use mockall::{mock, predicate::*}; use serde_json::json; mock! { @@ -253,13 +253,11 @@ mod tests { } #[tokio::test] - async fn test_get_inbox_urls_empty() { + async fn test_get_inbox_urls_empty() -> LemmyResult<()> { let mut collector = setup_collector(); let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -270,14 +268,16 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert!(result.is_empty()); + + Ok(()) } #[tokio::test] - async fn test_get_inbox_urls_send_all_instances() { + async fn test_get_inbox_urls_send_all_instances() -> LemmyResult<()> { let mut collector = setup_collector(); - let site_inbox = Url::parse("https://example.com/inbox").unwrap(); + let site_inbox = Url::parse("https://example.com/inbox")?; let site = Site { id: SiteId(1), name: "Test Site".to_string(), @@ -287,7 +287,7 @@ mod tests { icon: None, banner: None, description: None, - actor_id: Url::parse("https://example.com/site").unwrap().into(), + actor_id: Url::parse("https://example.com/site")?.into(), last_refreshed_at: Utc::now(), inbox_url: site_inbox.clone().into(), private_key: None, @@ -303,9 +303,7 @@ mod tests { let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -316,13 +314,15 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert_eq!(result.len(), 1); assert_eq!(result[0], site_inbox); + + Ok(()) } #[tokio::test] - async fn test_get_inbox_urls_community_followers() { + async fn test_get_inbox_urls_community_followers() -> LemmyResult<()> { let mut collector = setup_collector(); let community_id = CommunityId(1); let url1 = "https://follower1.example.com/inbox"; @@ -333,18 +333,22 @@ mod tests { .expect_get_instance_followed_community_inboxes() .return_once(move |_, _| { Ok(vec![ - (community_id, Url::parse(url1).unwrap().into()), - (community_id, Url::parse(url2).unwrap().into()), + ( + community_id, + Url::parse(url1).map_err(|_| diesel::NotFound)?.into(), + ), + ( + community_id, + Url::parse(url2).map_err(|_| diesel::NotFound)?.into(), + ), ]) }); - collector.update_communities().await.unwrap(); + collector.update_communities().await?; let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -355,24 +359,24 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert_eq!(result.len(), 2); - assert!(result.contains(&Url::parse(url1).unwrap())); - assert!(result.contains(&Url::parse(url2).unwrap())); + assert!(result.contains(&Url::parse(url1)?)); + assert!(result.contains(&Url::parse(url2)?)); + + Ok(()) } #[tokio::test] - async fn test_get_inbox_urls_send_inboxes() { + async fn test_get_inbox_urls_send_inboxes() -> LemmyResult<()> { let mut collector = setup_collector(); collector.domain = "example.com".to_string(); - let inbox_user_1 = Url::parse("https://example.com/user1/inbox").unwrap(); - let inbox_user_2 = Url::parse("https://example.com/user2/inbox").unwrap(); - let other_domain_inbox = Url::parse("https://other-domain.com/user3/inbox").unwrap(); + let inbox_user_1 = Url::parse("https://example.com/user1/inbox")?; + let inbox_user_2 = Url::parse("https://example.com/user2/inbox")?; + let other_domain_inbox = Url::parse("https://other-domain.com/user3/inbox")?; let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -387,20 +391,22 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert_eq!(result.len(), 2); assert!(result.contains(&inbox_user_1)); assert!(result.contains(&inbox_user_2)); assert!(!result.contains(&other_domain_inbox)); + + Ok(()) } #[tokio::test] - async fn test_get_inbox_urls_combined() { + async fn test_get_inbox_urls_combined() -> LemmyResult<()> { let mut collector = setup_collector(); collector.domain = "example.com".to_string(); let community_id = CommunityId(1); - let site_inbox = Url::parse("https://example.com/site_inbox").unwrap(); + let site_inbox = Url::parse("https://example.com/site_inbox")?; let site = Site { id: SiteId(1), name: "Test Site".to_string(), @@ -410,7 +416,7 @@ mod tests { icon: None, banner: None, description: None, - actor_id: Url::parse("https://example.com/site").unwrap().into(), + actor_id: Url::parse("https://example.com/site")?.into(), last_refreshed_at: Utc::now(), inbox_url: site_inbox.clone().into(), private_key: None, @@ -431,18 +437,18 @@ mod tests { .return_once(move |_, _| { Ok(vec![( community_id, - Url::parse(subdomain_inbox).unwrap().into(), + Url::parse(subdomain_inbox) + .map_err(|_| diesel::NotFound)? + .into(), )]) }); - collector.update_communities().await.unwrap(); - let user1_inbox = Url::parse("https://example.com/user1/inbox").unwrap(); - let user2_inbox = Url::parse("https://other-domain.com/user2/inbox").unwrap(); + collector.update_communities().await?; + let user1_inbox = Url::parse("https://example.com/user1/inbox")?; + let user2_inbox = Url::parse("https://other-domain.com/user2/inbox")?; let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -456,27 +462,29 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert_eq!(result.len(), 3); assert!(result.contains(&site_inbox)); - assert!(result.contains(&Url::parse(subdomain_inbox).unwrap())); + assert!(result.contains(&Url::parse(subdomain_inbox)?)); assert!(result.contains(&user1_inbox)); assert!(!result.contains(&user2_inbox)); + + Ok(()) } #[tokio::test] - async fn test_update_communities() { + async fn test_update_communities() -> LemmyResult<()> { let mut collector = setup_collector(); let community_id1 = CommunityId(1); let community_id2 = CommunityId(2); let community_id3 = CommunityId(3); let user1_inbox_str = "https://follower1.example.com/inbox"; - let user1_inbox = Url::parse(user1_inbox_str).unwrap(); + let user1_inbox = Url::parse(user1_inbox_str)?; let user2_inbox_str = "https://follower2.example.com/inbox"; - let user2_inbox = Url::parse(user2_inbox_str).unwrap(); + let user2_inbox = Url::parse(user2_inbox_str)?; let user3_inbox_str = "https://follower3.example.com/inbox"; - let user3_inbox = Url::parse(user3_inbox_str).unwrap(); + let user3_inbox = Url::parse(user3_inbox_str)?; collector .data_source @@ -485,42 +493,57 @@ mod tests { .returning(move |_, last_fetch| { if last_fetch == Utc.timestamp_nanos(0) { Ok(vec![ - (community_id1, Url::parse(user1_inbox_str).unwrap().into()), - (community_id2, Url::parse(user2_inbox_str).unwrap().into()), + ( + community_id1, + Url::parse(user1_inbox_str) + .map_err(|_| diesel::NotFound)? + .into(), + ), + ( + community_id2, + Url::parse(user2_inbox_str) + .map_err(|_| diesel::NotFound)? + .into(), + ), ]) } else { Ok(vec![( community_id3, - Url::parse(user3_inbox_str).unwrap().into(), + Url::parse(user3_inbox_str) + .map_err(|_| diesel::NotFound)? + .into(), )]) } }); // First update - collector.update_communities().await.unwrap(); + collector.update_communities().await?; assert_eq!(collector.followed_communities.len(), 2); assert!(collector.followed_communities[&community_id1].contains(&user1_inbox)); assert!(collector.followed_communities[&community_id2].contains(&user2_inbox)); // Simulate time passing - collector.last_full_communities_fetch = Utc::now() - chrono::TimeDelta::try_minutes(3).unwrap(); + collector.last_full_communities_fetch = + Utc::now() - chrono::TimeDelta::try_minutes(3).expect("TimeDelta out of bounds"); collector.last_incremental_communities_fetch = - Utc::now() - chrono::TimeDelta::try_minutes(3).unwrap(); + Utc::now() - chrono::TimeDelta::try_minutes(3).expect("TimeDelta out of bounds"); // Second update (incremental) - collector.update_communities().await.unwrap(); + collector.update_communities().await?; assert_eq!(collector.followed_communities.len(), 3); assert!(collector.followed_communities[&community_id1].contains(&user1_inbox)); assert!(collector.followed_communities[&community_id3].contains(&user3_inbox)); assert!(collector.followed_communities[&community_id2].contains(&user2_inbox)); + + Ok(()) } #[tokio::test] - async fn test_get_inbox_urls_no_duplicates() { + async fn test_get_inbox_urls_no_duplicates() -> LemmyResult<()> { let mut collector = setup_collector(); collector.domain = "example.com".to_string(); let community_id = CommunityId(1); - let site_inbox = Url::parse("https://example.com/site_inbox").unwrap(); + let site_inbox = Url::parse("https://example.com/site_inbox")?; let site_inbox_clone = site_inbox.clone(); let site = Site { id: SiteId(1), @@ -531,7 +554,7 @@ mod tests { icon: None, banner: None, description: None, - actor_id: Url::parse("https://example.com/site").unwrap().into(), + actor_id: Url::parse("https://example.com/site")?.into(), last_refreshed_at: Utc::now(), inbox_url: site_inbox.clone().into(), private_key: None, @@ -550,13 +573,11 @@ mod tests { .expect_get_instance_followed_community_inboxes() .return_once(move |_, _| Ok(vec![(community_id, site_inbox_clone.into())])); - collector.update_communities().await.unwrap(); + collector.update_communities().await?; let activity = SentActivity { id: ActivityId(1), - ap_id: Url::parse("https://example.com/activities/1") - .unwrap() - .into(), + ap_id: Url::parse("https://example.com/activities/1")?.into(), data: json!({}), sensitive: false, published: Utc::now(), @@ -567,8 +588,10 @@ mod tests { actor_apub_id: None, }; - let result = collector.get_inbox_urls(&activity).await.unwrap(); + let result = collector.get_inbox_urls(&activity).await?; assert_eq!(result.len(), 1); - assert!(result.contains(&Url::parse("https://example.com/site_inbox").unwrap())); + assert!(result.contains(&Url::parse("https://example.com/site_inbox")?)); + + Ok(()) } } diff --git a/crates/federate/src/lib.rs b/crates/federate/src/lib.rs index 66c0a2872..983749de3 100644 --- a/crates/federate/src/lib.rs +++ b/crates/federate/src/lib.rs @@ -192,8 +192,8 @@ impl SendManager { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod test { use super::*; @@ -354,10 +354,10 @@ mod test { 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(); + let form = InstanceForm { + updated: DateTime::from_timestamp(0, 0), + ..InstanceForm::new(instance.domain.clone()) + }; Instance::update(&mut data.context.pool(), instance.id, form).await?; data.run().await?; diff --git a/crates/federate/src/util.rs b/crates/federate/src/util.rs index e10a01c30..9473aafa3 100644 --- a/crates/federate/src/util.rs +++ b/crates/federate/src/util.rs @@ -183,8 +183,8 @@ pub(crate) async fn get_activity_cached( .try_get_with(activity_id, async { let row = SentActivity::read(pool, activity_id) .await - .context("could not read activity")?; - let Some(mut row) = row else { + .context("could not read activity"); + let Ok(mut row) = row else { return anyhow::Result::<_, anyhow::Error>::Ok(None); }; // swap to avoid cloning diff --git a/crates/federate/src/worker.rs b/crates/federate/src/worker.rs index 6c83aaa21..b0254ba0b 100644 --- a/crates/federate/src/worker.rs +++ b/crates/federate/src/worker.rs @@ -290,10 +290,10 @@ impl InstanceWorker { if updated.add(Days::new(1)) < Utc::now() { self.instance.updated = Some(Utc::now()); - let form = InstanceForm::builder() - .domain(self.instance.domain.clone()) - .updated(Some(naive_now())) - .build(); + let form = InstanceForm { + updated: Some(naive_now()), + ..InstanceForm::new(self.instance.domain.clone()) + }; Instance::update(&mut self.pool(), self.instance.id, form).await?; } Ok(()) @@ -439,8 +439,8 @@ impl InstanceWorker { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod test { use super::*; @@ -449,7 +449,7 @@ mod test { protocol::context::WithContext, }; use actix_web::{dev::ServerHandle, web, App, HttpResponse, HttpServer}; - use lemmy_api_common::utils::{generate_inbox_url, generate_shared_inbox_url}; + use lemmy_api_common::utils::generate_inbox_url; use lemmy_db_schema::{ newtypes::DbUrl, source::{ @@ -491,8 +491,7 @@ mod test { let person_form = PersonInsertForm { actor_id: Some(actor_id.clone()), private_key: (Some(actor_keypair.private_key)), - inbox_url: Some(generate_inbox_url(&actor_id)?), - shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?), + inbox_url: Some(generate_inbox_url()?), ..PersonInsertForm::new("alice".to_string(), actor_keypair.public_key, instance.id) }; let person = Person::create(&mut context.pool(), &person_form).await?; @@ -658,10 +657,7 @@ mod test { #[tokio::test] #[serial] async fn test_update_instance(data: &mut Data) -> LemmyResult<()> { - let form = InstanceForm::builder() - .domain(data.instance.domain.clone()) - .updated(None) - .build(); + let form = InstanceForm::new(data.instance.domain.clone()); Instance::update(&mut data.context.pool(), data.instance.id, form).await?; send_activity(data.person.actor_id.clone(), &data.context, true).await?; diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index f7e7d4059..00518032d 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -9,7 +9,7 @@ use lemmy_db_schema::{ CommentSortType, CommunityVisibility, ListingType, - SortType, + PostSortType, }; use lemmy_db_views::{ post_view::PostQuery, @@ -27,6 +27,7 @@ use lemmy_utils::{ }; use rss::{ extension::{dublincore::DublinCoreExtension, ExtensionBuilder, ExtensionMap}, + Category, Channel, EnclosureBuilder, Guid, @@ -45,12 +46,12 @@ struct Params { } impl Params { - fn sort_type(&self) -> Result { + fn sort_type(&self) -> Result { let sort_query = self .sort .clone() - .unwrap_or_else(|| SortType::Hot.to_string()); - SortType::from_str(&sort_query).map_err(ErrorBadRequest) + .unwrap_or_else(|| PostSortType::Hot.to_string()); + PostSortType::from_str(&sort_query).map_err(ErrorBadRequest) } fn get_limit(&self) -> i64 { self.limit.unwrap_or(RSS_FETCH_LIMIT) @@ -147,13 +148,11 @@ async fn get_local_feed( async fn get_feed_data( context: &LemmyContext, listing_type: ListingType, - sort_type: SortType, + sort_type: PostSortType, limit: i64, page: i64, ) -> LemmyResult { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; check_private_instance(&None, &site_view.local_site)?; @@ -253,17 +252,15 @@ async fn get_feed( #[tracing::instrument(skip_all)] async fn get_feed_user( context: &LemmyContext, - sort_type: &SortType, + sort_type: &PostSortType, limit: &i64, page: &i64, user_name: &str, ) -> LemmyResult { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let person = Person::read_from_name(&mut context.pool(), user_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindPerson)?; + .ok_or(LemmyErrorType::NotFound)?; check_private_instance(&None, &site_view.local_site)?; @@ -293,19 +290,17 @@ async fn get_feed_user( #[tracing::instrument(skip_all)] async fn get_feed_community( context: &LemmyContext, - sort_type: &SortType, + sort_type: &PostSortType, limit: &i64, page: &i64, community_name: &str, ) -> LemmyResult { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let community = Community::read_from_name(&mut context.pool(), community_name, false) .await? - .ok_or(LemmyErrorType::CouldntFindCommunity)?; + .ok_or(LemmyErrorType::NotFound)?; if community.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::CouldntFindCommunity.into()); + return Err(LemmyErrorType::NotFound.into()); } check_private_instance(&None, &site_view.local_site)?; @@ -340,14 +335,12 @@ async fn get_feed_community( #[tracing::instrument(skip_all)] async fn get_feed_front( context: &LemmyContext, - sort_type: &SortType, + sort_type: &PostSortType, limit: &i64, page: &i64, jwt: &str, ) -> LemmyResult { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let local_user = local_user_view_from_jwt(jwt, context).await?; check_private_instance(&Some(local_user.clone()), &site_view.local_site)?; @@ -382,9 +375,7 @@ async fn get_feed_front( #[tracing::instrument(skip_all)] async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult { - let site_view = SiteView::read_local(&mut context.pool()) - .await? - .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; let local_user = local_user_view_from_jwt(jwt, context).await?; let person_id = local_user.local_user.person_id; let show_bot_accounts = local_user.local_user.show_bot_accounts; @@ -569,6 +560,10 @@ fn create_post_items(posts: Vec, protocol_and_hostname: &str) -> Lemmy BTreeMap::from([("content".to_string(), vec![thumbnail_ext.build()])]), ); } + let category = Category { + name: p.community.title, + domain: Some(p.community.actor_id.to_string()), + }; let i = Item { title: Some(sanitize_html(sanitize_xml(p.post.name).as_str())), @@ -580,6 +575,7 @@ fn create_post_items(posts: Vec, protocol_and_hostname: &str) -> Lemmy link: Some(post_url.clone()), extensions, enclosure: enclosure_opt, + categories: vec![category], ..Default::default() }; diff --git a/crates/routes/src/images.rs b/crates/routes/src/images.rs index 768a607c2..a0f804b6b 100644 --- a/crates/routes/src/images.rs +++ b/crates/routes/src/images.rs @@ -5,8 +5,7 @@ use actix_web::{ Method, StatusCode, }, - web, - web::Query, + web::{self, Query}, HttpRequest, HttpResponse, }; diff --git a/crates/routes/src/lib.rs b/crates/routes/src/lib.rs index 4f8d60246..a88225622 100644 --- a/crates/routes/src/lib.rs +++ b/crates/routes/src/lib.rs @@ -1,6 +1,6 @@ use lemmy_api_common::{claims::Claims, context::LemmyContext, utils::check_user_valid}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::LemmyResult; pub mod feeds; pub mod images; @@ -10,9 +10,7 @@ pub mod webfinger; #[tracing::instrument(skip_all)] async fn local_user_view_from_jwt(jwt: &str, context: &LemmyContext) -> LemmyResult { let local_user_id = Claims::validate(jwt, context).await?; - let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) - .await? - .ok_or(LemmyErrorType::CouldntFindLocalUser)?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; check_user_valid(&local_user_view.person)?; Ok(local_user_view) diff --git a/crates/routes/src/nodeinfo.rs b/crates/routes/src/nodeinfo.rs index bcb835309..e5b183a0b 100644 --- a/crates/routes/src/nodeinfo.rs +++ b/crates/routes/src/nodeinfo.rs @@ -1,11 +1,10 @@ -use actix_web::{error::ErrorBadRequest, web, Error, HttpResponse, Result}; -use anyhow::anyhow; +use actix_web::{web, Error, HttpResponse, Result}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::RegistrationMode; use lemmy_db_views::structs::SiteView; use lemmy_utils::{ cache_header::{cache_1hour, cache_3days}, - error::{LemmyError, LemmyResult}, + error::LemmyResult, VERSION, }; use serde::{Deserialize, Serialize}; @@ -44,10 +43,7 @@ async fn node_info_well_known(context: web::Data) -> LemmyResult) -> Result { - let site_view = SiteView::read_local(&mut context.pool()) - .await - .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))? - .ok_or(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?; + let site_view = SiteView::read_local(&mut context.pool()).await?; // Since there are 3 registration options, // we need to set open_registrations as true if RegistrationMode is not Closed. diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index a556ef49c..1c99e89c2 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -81,6 +81,11 @@ markdown-it = { version = "0.6.1", optional = true } ts-rs = { workspace = true, optional = true } enum-map = { workspace = true, optional = true } cfg-if = "1" +clearurls = { version = "0.0.4", features = ["linkify"] } +markdown-it-block-spoiler = "1.0.0" +markdown-it-sub = "1.0.0" +markdown-it-sup = "1.0.0" +markdown-it-ruby = "1.0.0" [dev-dependencies] reqwest = { workspace = true } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 53611b59c..1f29bf589 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -39,21 +39,9 @@ pub enum LemmyErrorType { SiteBan, Deleted, BannedFromCommunity, - CouldntFindCommunity, - CouldntFindPerson, - CouldntFindComment, - CouldntFindCommentReport, - CouldntFindPostReport, - CouldntFindPrivateMessageReport, - CouldntFindLocalUser, - CouldntFindPersonMention, - CouldntFindRegistrationApplication, - CouldntFindCommentReply, - CouldntFindPrivateMessage, PersonIsBlocked, CommunityIsBlocked, InstanceIsBlocked, - DownvotesAreDisabled, InstanceIsPrivate, /// Password must be between 10 and 60 characters InvalidPassword, @@ -68,17 +56,18 @@ pub enum LemmyErrorType { OnlyAdminsCanCreateCommunities, CommunityAlreadyExists, LanguageNotAllowed, - OnlyModsCanPostInCommunity, CouldntUpdatePost, NoPostEditAllowed, - CouldntFindPost, EditPrivateMessageNotAllowed, SiteAlreadyExists, ApplicationQuestionRequired, InvalidDefaultPostListingType, RegistrationClosed, RegistrationApplicationAnswerRequired, + RegistrationUsernameRequired, EmailAlreadyExists, + UsernameAlreadyExists, + PersonIsBannedFromCommunity, NoIdGiven, IncorrectLogin, ObjectNotLocal, @@ -111,7 +100,6 @@ pub enum LemmyErrorType { CouldntUpdateCommunityHiddenStatus, PersonBlockAlreadyExists, UserAlreadyExists, - TokenNotFound, CouldntLikePost, CouldntSavePost, CouldntMarkPostAsRead, @@ -132,7 +120,6 @@ pub enum LemmyErrorType { InvalidUrl, EmailSendFailed, Slurs, - CouldntFindObject, RegistrationDenied(Option), SiteNameRequired, SiteNameLengthOverflow, @@ -153,6 +140,14 @@ pub enum LemmyErrorType { CantBlockLocalInstance, Unknown(String), UrlLengthOverflow, + OauthAuthorizationInvalid, + OauthLoginFailed, + OauthRegistrationClosed, + CouldntDeleteOauthProvider, + NotFound, + CommunityHasNoFollowers, + PostScheduleTimeMustBeInFuture, + TooManyScheduledPosts, FederationError(Option), } @@ -164,7 +159,6 @@ pub enum LemmyErrorType { pub enum FederationError { // TODO: merge into a single NotFound error CouldntFindActivity, - PersonIsBannedFromCommunity, InvalidCommunity, CannotCreatePostOrCommentInDeletedOrRemovedCommunity, CannotReceivePage, @@ -181,7 +175,6 @@ pub enum FederationError { DomainNotInAllowList(String), FederationDisabledByStrictAllowList, ContradictingFilters, - CommunityHasNoFollowers, UrlWithoutDomain, InboxTimeout, CantDeleteSite, @@ -209,8 +202,12 @@ cfg_if! { { fn from(t: T) -> Self { let cause = t.into(); + let error_type = match cause.downcast_ref::() { + Some(&diesel::NotFound) => LemmyErrorType::NotFound, + _ => LemmyErrorType::Unknown(format!("{}", &cause)) + }; LemmyError { - error_type: LemmyErrorType::Unknown(format!("{}", &cause)), + error_type, inner: cause, context: Backtrace::capture(), } @@ -306,7 +303,6 @@ cfg_if! { #[cfg(test)] mod tests { - #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] use super::*; use actix_web::{body::MessageBody, ResponseError}; @@ -315,39 +311,57 @@ cfg_if! { use strum::IntoEnumIterator; #[test] - fn deserializes_no_message() { + fn deserializes_no_message() -> LemmyResult<()> { let err = LemmyError::from(LemmyErrorType::Banned).error_response(); - let json = String::from_utf8(err.into_body().try_into_bytes().unwrap().to_vec()).unwrap(); - assert_eq!(&json, "{\"error\":\"banned\"}") + let json = String::from_utf8(err.into_body().try_into_bytes().unwrap_or_default().to_vec())?; + assert_eq!(&json, "{\"error\":\"banned\"}"); + + Ok(()) } #[test] - fn deserializes_with_message() { + fn deserializes_with_message() -> LemmyResult<()> { let reg_banned = LemmyErrorType::PictrsResponseError(String::from("reason")); let err = LemmyError::from(reg_banned).error_response(); - let json = String::from_utf8(err.into_body().try_into_bytes().unwrap().to_vec()).unwrap(); + let json = String::from_utf8(err.into_body().try_into_bytes().unwrap_or_default().to_vec())?; assert_eq!( &json, "{\"error\":\"pictrs_response_error\",\"message\":\"reason\"}" - ) + ); + + Ok(()) + } + + #[test] + fn test_convert_diesel_errors() { + let not_found_error = LemmyError::from(diesel::NotFound); + assert_eq!(LemmyErrorType::NotFound, not_found_error.error_type); + assert_eq!(404, not_found_error.status_code()); + + let other_error = LemmyError::from(diesel::result::Error::NotInTransaction); + assert!(matches!(other_error.error_type, LemmyErrorType::Unknown{..})); + assert_eq!(400, other_error.status_code()); } /// Check if errors match translations. Disabled because many are not translated at all. #[test] #[ignore] - fn test_translations_match() { + fn test_translations_match() -> LemmyResult<()> { #[derive(Deserialize)] struct Err { error: String, } - let translations = read_to_string("translations/translations/en.json").unwrap(); - LemmyErrorType::iter().for_each(|e| { - let msg = serde_json::to_string(&e).unwrap(); - let msg: Err = serde_json::from_str(&msg).unwrap(); + let translations = read_to_string("translations/translations/en.json")?; + + for e in LemmyErrorType::iter() { + let msg = serde_json::to_string(&e)?; + let msg: Err = serde_json::from_str(&msg)?; let msg = msg.error; assert!(translations.contains(&format!("\"{msg}\"")), "{msg}"); - }); + } + + Ok(()) } } } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 5a5e76d2a..7f0691496 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -29,6 +29,8 @@ pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_secs(60); pub const CACHE_DURATION_API: Duration = Duration::from_secs(1); +pub const MAX_COMMENT_DEPTH_LIMIT: usize = 50; + #[macro_export] macro_rules! location_info { () => { diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index de64bac46..a6cf92150 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -221,8 +221,6 @@ fn parse_ip(addr: &str) -> Option { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { #[test] diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index a3c6f6a27..01d379986 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -136,7 +136,6 @@ impl MapLevel for Map { .entry(addr_part) .or_insert(RateLimitedGroup::new(now, adjusted_configs)); - #[allow(clippy::indexing_slicing)] let total_passes = group.check_total(action_type, now, adjusted_configs[action_type]); let children_pass = group.children.check( @@ -161,7 +160,6 @@ impl MapLevel for Map { // Evaluated if `some_children_remaining` is false let total_has_refill_in_future = || { group.total.into_iter().any(|(action_type, bucket)| { - #[allow(clippy::indexing_slicing)] let config = configs[action_type]; bucket.update(now, config).tokens != config.capacity }) @@ -214,7 +212,6 @@ impl RateLimitedGroup { now: InstantSecs, config: BucketConfig, ) -> bool { - #[allow(clippy::indexing_slicing)] // `EnumMap` has no `get` function let bucket = &mut self.total[action_type]; let new_bucket = bucket.update(now, config); @@ -311,11 +308,10 @@ fn split_ipv6(ip: Ipv6Addr) -> ([u8; 6], u8, u8) { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup}; + use crate::error::LemmyResult; use pretty_assertions::assert_eq; #[test] @@ -330,7 +326,7 @@ mod tests { } #[test] - fn test_rate_limiter() { + fn test_rate_limiter() -> LemmyResult<()> { let bucket_configs = enum_map::enum_map! { ActionType::Message => BucketConfig { capacity: 2, @@ -354,14 +350,13 @@ mod tests { "1:2:3:0405:6::", ]; for ip in ips { - let ip = ip.parse().unwrap(); + let ip = ip.parse()?; let message_passed = rate_limiter.check(ActionType::Message, ip, now); let post_passed = rate_limiter.check(ActionType::Post, ip, now); assert!(message_passed); assert!(post_passed); } - #[allow(clippy::indexing_slicing)] let expected_buckets = |factor: u32, tokens_consumed: u32| { let adjusted_configs = bucket_configs.map(|_, config| BucketConfig { capacity: config.capacity.saturating_mul(factor), @@ -412,7 +407,7 @@ mod tests { // Do 2 `Message` actions for 1 IP address and expect only the 2nd one to fail for expected_to_pass in [true, false] { - let ip = "1:2:3:0400::".parse().unwrap(); + let ip = "1:2:3:0400::".parse()?; let passed = rate_limiter.check(ActionType::Message, ip, now); assert_eq!(passed, expected_to_pass); } @@ -424,7 +419,7 @@ mod tests { assert!(rate_limiter.ipv6_buckets.is_empty()); // `remove full buckets` should not remove empty buckets - let ip = "1.1.1.1".parse().unwrap(); + let ip = "1.1.1.1".parse()?; // empty the bucket with 2 requests assert!(rate_limiter.check(ActionType::Post, ip, now)); assert!(rate_limiter.check(ActionType::Post, ip, now)); @@ -434,11 +429,13 @@ mod tests { // `remove full buckets` should not remove partial buckets now.secs += 2; - let ip = "1.1.1.1".parse().unwrap(); + let ip = "1.1.1.1".parse()?; // Only make one request, so bucket still has 1 token assert!(rate_limiter.check(ActionType::Post, ip, now)); rate_limiter.remove_full_buckets(now); assert!(!rate_limiter.ipv4_buckets.is_empty()); + + Ok(()) } } diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 547ae20d9..8c28d908a 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -90,6 +90,10 @@ pub struct PictrsConfig { /// Timeout for uploading images to pictrs (in seconds) #[default(30)] pub upload_timeout: u64, + + /// Resize post thumbnails to this maximum width/height. + #[default(256)] + pub max_thumbnail_size: u32, } #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document, PartialEq)] diff --git a/crates/utils/src/utils/markdown/image_links.rs b/crates/utils/src/utils/markdown/image_links.rs new file mode 100644 index 000000000..a21bb6f41 --- /dev/null +++ b/crates/utils/src/utils/markdown/image_links.rs @@ -0,0 +1,168 @@ +use super::{link_rule::Link, MARKDOWN_PARSER}; +use crate::settings::SETTINGS; +use markdown_it::{plugins::cmark::inline::image::Image, NodeValue}; +use url::Url; +use urlencoding::encode; + +/// Rewrites all links to remote domains in markdown, so they go through `/api/v3/image_proxy`. +pub fn markdown_rewrite_image_links(mut src: String) -> (String, Vec) { + let links_offsets = find_urls::(&src); + + let mut links = vec![]; + // Go through the collected links in reverse order + for (start, end) in links_offsets.into_iter().rev() { + let (url, extra) = markdown_handle_title(&src, start, end); + match Url::parse(url) { + Ok(parsed) => { + links.push(parsed.clone()); + // If link points to remote domain, replace with proxied link + if parsed.domain() != Some(&SETTINGS.hostname) { + let mut proxied = format!( + "{}/api/v3/image_proxy?url={}", + SETTINGS.get_protocol_and_hostname(), + encode(url), + ); + // restore custom emoji format + if let Some(extra) = extra { + proxied = format!("{proxied} {extra}"); + } + src.replace_range(start..end, &proxied); + } + } + Err(_) => { + // If its not a valid url, replace with empty text + src.replace_range(start..end, ""); + } + } + } + + (src, links) +} + +pub fn markdown_handle_title(src: &str, start: usize, end: usize) -> (&str, Option<&str>) { + let content = src.get(start..end).unwrap_or_default(); + // necessary for custom emojis which look like `![name](url "title")` + let (url, extra) = if content.contains(' ') { + let split = content.split_once(' ').expect("split is valid"); + (split.0, Some(split.1)) + } else { + (content, None) + }; + (url, extra) +} + +pub fn markdown_find_links(src: &str) -> Vec<(usize, usize)> { + find_urls::(src) +} + +// Walk the syntax tree to find positions of image or link urls +fn find_urls(src: &str) -> Vec<(usize, usize)> { + let ast = MARKDOWN_PARSER.parse(src); + let mut links_offsets = vec![]; + ast.walk(|node, _depth| { + if let Some(image) = node.cast::() { + let node_offsets = node.srcmap.expect("srcmap is none").get_byte_offsets(); + let start_offset = node_offsets.1 - image.url_len() - 1 - image.title_len(); + let end_offset = node_offsets.1 - 1; + + links_offsets.push((start_offset, end_offset)); + } + }); + links_offsets +} + +pub trait UrlAndTitle { + fn url_len(&self) -> usize; + fn title_len(&self) -> usize; +} + +impl UrlAndTitle for Image { + fn url_len(&self) -> usize { + self.url.len() + } + + fn title_len(&self) -> usize { + self.title.as_ref().map(|t| t.len() + 3).unwrap_or_default() + } +} +impl UrlAndTitle for Link { + fn url_len(&self) -> usize { + self.url.len() + } + fn title_len(&self) -> usize { + self.title.as_ref().map(|t| t.len() + 3).unwrap_or_default() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_find_links() { + let links = markdown_find_links("[test](https://example.com)"); + assert_eq!(vec![(7, 26)], links); + + let links = find_urls::("![test](https://example.com)"); + assert_eq!(vec![(8, 27)], links); + } + + #[test] + fn test_markdown_proxy_images() { + let tests: Vec<_> = + vec![ + ( + "remote image proxied", + "![link](http://example.com/image.jpg)", + "![link](https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)", + ), + ( + "local image unproxied", + "![link](http://lemmy-alpha/image.jpg)", + "![link](http://lemmy-alpha/image.jpg)", + ), + ( + "multiple image links", + "![link](http://example.com/image1.jpg) ![link](http://example.com/image2.jpg)", + "![link](https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage1.jpg) ![link](https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage2.jpg)", + ), + ( + "empty link handled", + "![image]()", + "![image]()" + ), + ( + "empty label handled", + "![](http://example.com/image.jpg)", + "![](https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" + ), + ( + "invalid image link removed", + "![image](http-not-a-link)", + "![image]()" + ), + ( + "label with nested markdown handled", + "![a *b* c](http://example.com/image.jpg)", + "![a *b* c](https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" + ), + ( + "custom emoji support", + r#"![party-blob](https://www.hexbear.net/pictrs/image/83405746-0620-4728-9358-5f51b040ffee.gif "emoji party-blob")"#, + r#"![party-blob](https://lemmy-alpha/api/v3/image_proxy?url=https%3A%2F%2Fwww.hexbear.net%2Fpictrs%2Fimage%2F83405746-0620-4728-9358-5f51b040ffee.gif "emoji party-blob")"# + ) + ]; + + tests.iter().for_each(|&(msg, input, expected)| { + let result = markdown_rewrite_image_links(input.to_string()); + + assert_eq!( + result.0, expected, + "Testing {}, with original input '{}'", + msg, input + ); + }); + } +} diff --git a/crates/utils/src/utils/markdown/mod.rs b/crates/utils/src/utils/markdown/mod.rs index 7ed553e06..a51b507ce 100644 --- a/crates/utils/src/utils/markdown/mod.rs +++ b/crates/utils/src/utils/markdown/mod.rs @@ -1,18 +1,19 @@ -use crate::{error::LemmyResult, settings::SETTINGS, LemmyErrorType}; -use markdown_it::{plugins::cmark::inline::image::Image, MarkdownIt}; +use crate::{error::LemmyResult, LemmyErrorType}; +use markdown_it::MarkdownIt; use regex::RegexSet; use std::sync::LazyLock; -use url::Url; -use urlencoding::encode; +pub mod image_links; mod link_rule; -mod spoiler_rule; static MARKDOWN_PARSER: LazyLock = LazyLock::new(|| { let mut parser = MarkdownIt::new(); markdown_it::plugins::cmark::add(&mut parser); markdown_it::plugins::extra::add(&mut parser); - spoiler_rule::add(&mut parser); + markdown_it_block_spoiler::add(&mut parser); + markdown_it_sub::add(&mut parser); + markdown_it_sup::add(&mut parser); + markdown_it_ruby::add(&mut parser); link_rule::add(&mut parser); parser @@ -35,70 +36,6 @@ pub fn markdown_to_html(text: &str) -> String { MARKDOWN_PARSER.parse(text).xrender() } -/// Rewrites all links to remote domains in markdown, so they go through `/api/v3/image_proxy`. -pub fn markdown_rewrite_image_links(mut src: String) -> (String, Vec) { - let ast = MARKDOWN_PARSER.parse(&src); - let mut links_offsets = vec![]; - - // Walk the syntax tree to find positions of image links - ast.walk(|node, _depth| { - if let Some(image) = node.cast::() { - // srcmap is always present for image - // https://github.com/markdown-it-rust/markdown-it/issues/36#issuecomment-1777844387 - let node_offsets = node.srcmap.expect("srcmap is none").get_byte_offsets(); - // necessary for custom emojis which look like `![name](url "title")` - let start_offset = node_offsets.1 - - image.url.len() - - 1 - - image - .title - .as_ref() - .map(|t| t.len() + 3) - .unwrap_or_default(); - let end_offset = node_offsets.1 - 1; - - links_offsets.push((start_offset, end_offset)); - } - }); - - let mut links = vec![]; - // Go through the collected links in reverse order - while let Some((start, end)) = links_offsets.pop() { - let content = src.get(start..end).unwrap_or_default(); - // necessary for custom emojis which look like `![name](url "title")` - let (url, extra) = if content.contains(' ') { - let split = content.split_once(' ').expect("split is valid"); - (split.0, Some(split.1)) - } else { - (content, None) - }; - match Url::parse(url) { - Ok(parsed) => { - links.push(parsed.clone()); - // If link points to remote domain, replace with proxied link - if parsed.domain() != Some(&SETTINGS.hostname) { - let mut proxied = format!( - "{}/api/v3/image_proxy?url={}", - SETTINGS.get_protocol_and_hostname(), - encode(url), - ); - // restore custom emoji format - if let Some(extra) = extra { - proxied = format!("{proxied} {extra}"); - } - src.replace_range(start..end, &proxied); - } - } - Err(_) => { - // If its not a valid url, replace with empty text - src.replace_range(start..end, ""); - } - } - } - - (src, links) -} - pub fn markdown_check_for_blocked_urls(text: &str, blocklist: &RegexSet) -> LemmyResult<()> { if blocklist.is_match(text) { Err(LemmyErrorType::BlockedUrl)? @@ -107,11 +44,10 @@ pub fn markdown_check_for_blocked_urls(text: &str, blocklist: &RegexSet) -> Lemm } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; + use image_links::markdown_rewrite_image_links; use pretty_assertions::assert_eq; #[test] @@ -168,12 +104,22 @@ mod tests { ( "basic spoiler", "::: spoiler click to see more\nhow spicy!\n:::\n", - "
click to see more

how spicy!\n

\n" + "
click to see morehow spicy!\n
\n" ), ( "escape html special chars", " hello &\"", "

<script>alert(‘xss’);</script> hello &"

\n" + ),("subscript","log~2~(a)","

log2(a)

\n"), + ( + "superscript", + "Markdown^TM^", + "

MarkdownTM

\n" + ), + ( + "ruby text", + "{漢|Kan}{字|ji}", + "

(Kan)(ji)

\n" ) ]; @@ -246,8 +192,8 @@ mod tests { } #[test] - fn test_url_blocking() { - let set = RegexSet::new(vec![r"(https://)?example\.com/?"]).unwrap(); + fn test_url_blocking() -> LemmyResult<()> { + let set = RegexSet::new(vec![r"(https://)?example\.com/?"])?; assert!( markdown_check_for_blocked_urls(&String::from("[](https://example.com)"), &set).is_err() @@ -275,7 +221,7 @@ mod tests { ) .is_err()); - let set = RegexSet::new(vec![r"(https://)?example\.com/spam\.jpg"]).unwrap(); + let set = RegexSet::new(vec![r"(https://)?example\.com/spam\.jpg"])?; assert!(markdown_check_for_blocked_urls( &String::from("![](https://example.com/spam.jpg)"), &set @@ -286,8 +232,7 @@ mod tests { r"(https://)?quo\.example\.com/?", r"(https://)?foo\.example\.com/?", r"(https://)?bar\.example\.com/?", - ]) - .unwrap(); + ])?; assert!( markdown_check_for_blocked_urls(&String::from("https://baz.example.com"), &set).is_ok() @@ -297,15 +242,17 @@ mod tests { markdown_check_for_blocked_urls(&String::from("https://bar.example.com"), &set).is_err() ); - let set = RegexSet::new(vec![r"(https://)?example\.com/banned_page"]).unwrap(); + let set = RegexSet::new(vec![r"(https://)?example\.com/banned_page"])?; assert!( markdown_check_for_blocked_urls(&String::from("https://example.com/page"), &set).is_ok() ); - let set = RegexSet::new(vec![r"(https://)?ex\.mple\.com/?"]).unwrap(); + let set = RegexSet::new(vec![r"(https://)?ex\.mple\.com/?"])?; assert!(markdown_check_for_blocked_urls("example.com", &set).is_ok()); + + Ok(()) } #[test] diff --git a/crates/utils/src/utils/markdown/spoiler_rule.rs b/crates/utils/src/utils/markdown/spoiler_rule.rs deleted file mode 100644 index caced310a..000000000 --- a/crates/utils/src/utils/markdown/spoiler_rule.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Custom Markdown plugin to manage spoilers. -// -// Matches the capability described in Lemmy UI: -// https://github.com/LemmyNet/lemmy-ui/blob/main/src/shared/utils.ts#L159 -// that is based off of: -// https://github.com/markdown-it/markdown-it-container/tree/master#example -// -// FORMAT: -// Input Markdown: ::: spoiler VISIBLE_TEXT\nHIDDEN_SPOILER\n:::\n -// Output HTML:
VISIBLE_TEXT

nHIDDEN_SPOILER

-// -// Anatomy of a spoiler: -// keyword -// ^ -// ::: spoiler VISIBLE_HINT -// ^ ^ -// begin fence visible text -// -// HIDDEN_SPOILER -// ^ -// hidden text -// -// ::: -// ^ -// end fence - -use markdown_it::{ - parser::{ - block::{BlockRule, BlockState}, - inline::InlineRoot, - }, - MarkdownIt, - Node, - NodeValue, - Renderer, -}; -use regex::Regex; -use std::sync::LazyLock; - -#[derive(Debug)] -struct SpoilerBlock { - visible_text: String, -} - -const SPOILER_PREFIX: &str = "::: spoiler "; -const SPOILER_SUFFIX: &str = ":::"; -const SPOILER_SUFFIX_NEWLINE: &str = ":::\n"; - -static SPOILER_REGEX: LazyLock = - LazyLock::new(|| Regex::new(r"^::: spoiler .*$").expect("compile spoiler markdown regex.")); - -impl NodeValue for SpoilerBlock { - // Formats any node marked as a 'SpoilerBlock' into HTML. - // See the SpoilerBlockScanner#run implementation to see how these nodes get added to the tree. - fn render(&self, node: &Node, fmt: &mut dyn Renderer) { - fmt.cr(); - fmt.open("details", &node.attrs); - fmt.open("summary", &[]); - // Not allowing special styling to the visible text to keep it simple. - // If allowed, would need to parse the child nodes to assign to visible vs hidden text sections. - fmt.text(&self.visible_text); - fmt.close("summary"); - fmt.open("p", &[]); - fmt.contents(&node.children); - fmt.close("p"); - fmt.close("details"); - fmt.cr(); - } -} - -struct SpoilerBlockScanner; - -impl BlockRule for SpoilerBlockScanner { - // Invoked on every line in the provided Markdown text to check if the BlockRule applies. - // - // NOTE: This does NOT support nested spoilers at this time. - fn run(state: &mut BlockState) -> Option<(Node, usize)> { - let first_line: &str = state.get_line(state.line).trim(); - - // 1. Check if the first line contains the spoiler syntax... - if !SPOILER_REGEX.is_match(first_line) { - return None; - } - - let begin_spoiler_line_idx: usize = state.line + 1; - let mut end_fence_line_idx: usize = begin_spoiler_line_idx; - let mut has_end_fence: bool = false; - - // 2. Search for the end of the spoiler and find the index of the last line of the spoiler. - // There could potentially be multiple lines between the beginning and end of the block. - // - // Block ends with a line with ':::' or ':::\n'; it must be isolated from other markdown. - while end_fence_line_idx < state.line_max && !has_end_fence { - let next_line: &str = state.get_line(end_fence_line_idx).trim(); - - if next_line.eq(SPOILER_SUFFIX) || next_line.eq(SPOILER_SUFFIX_NEWLINE) { - has_end_fence = true; - break; - } - - end_fence_line_idx += 1; - } - - // 3. If available, construct and return the spoiler node to add to the tree. - if has_end_fence { - let (spoiler_content, mapping) = state.get_lines( - begin_spoiler_line_idx, - end_fence_line_idx, - state.blk_indent, - true, - ); - - let mut node = Node::new(SpoilerBlock { - visible_text: String::from(first_line.replace(SPOILER_PREFIX, "").trim()), - }); - - // Add the spoiler content as children; marking as a child tells the tree to process the - // node again, which means other Markdown syntax (ex: emphasis, links) can be rendered. - node - .children - .push(Node::new(InlineRoot::new(spoiler_content, mapping))); - - // NOTE: Not using begin_spoiler_line_idx here because of incorrect results when - // state.line == 0 (subtracts an idx) vs the expected correct result (adds an idx). - Some((node, end_fence_line_idx - state.line + 1)) - } else { - None - } - } -} - -pub fn add(markdown_parser: &mut MarkdownIt) { - markdown_parser.block.add_rule::(); -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] -mod tests { - - use crate::utils::markdown::spoiler_rule::add; - use markdown_it::MarkdownIt; - use pretty_assertions::assert_eq; - - #[test] - fn test_spoiler_markdown() { - let tests: Vec<_> = vec![ - ( - "invalid spoiler", - "::: spoiler click to see more\nbut I never finished", - "

::: spoiler click to see more\nbut I never finished

\n", - ), - ( - "another invalid spoiler", - "::: spoiler\nnever added the lead in\n:::", - "

::: spoiler\nnever added the lead in\n:::

\n", - ), - ( - "basic spoiler, but no newline at the end", - "::: spoiler click to see more\nhow spicy!\n:::", - "
click to see more

how spicy!\n

\n" - ), - ( - "basic spoiler with a newline at the end", - "::: spoiler click to see more\nhow spicy!\n:::\n", - "
click to see more

how spicy!\n

\n" - ), - ( - "spoiler with extra markdown on the call to action (no extra parsing)", - "::: spoiler _click to see more_\nhow spicy!\n:::\n", - "
_click to see more_

how spicy!\n

\n" - ), - ( - "spoiler with extra markdown in the fenced spoiler block", - "::: spoiler click to see more\n**how spicy!**\n*i have many lines*\n:::\n", - "
click to see more

how spicy!\ni have many lines\n

\n" - ), - ( - "spoiler mixed with other content", - "hey you\npsst, wanna hear a secret?\n::: spoiler lean in and i'll tell you\n**you are breathtaking!**\n:::\nwhatcha think about that?", - "

hey you\npsst, wanna hear a secret?

\n
lean in and i'll tell you

you are breathtaking!\n

\n

whatcha think about that?

\n" - ), - ( - "spoiler mixed with indented content", - "- did you know that\n::: spoiler the call was\n***coming from inside the house!***\n:::\n - crazy, right?", - "
    \n
  • did you know that
  • \n
\n
the call was

coming from inside the house!\n

\n
    \n
  • crazy, right?
  • \n
\n" - ) - ]; - - tests.iter().for_each(|&(msg, input, expected)| { - let md = &mut MarkdownIt::new(); - markdown_it::plugins::cmark::add(md); - add(md); - - assert_eq!( - md.parse(input).xrender(), - expected, - "Testing {}, with original input '{}'", - msg, - input - ); - }); - } -} diff --git a/crates/utils/src/utils/mention.rs b/crates/utils/src/utils/mention.rs index c7cc2043f..13762ed27 100644 --- a/crates/utils/src/utils/mention.rs +++ b/crates/utils/src/utils/mention.rs @@ -34,8 +34,7 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod test { use crate::utils::mention::scrape_text_for_mentions; diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index ba94372fa..2350822eb 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -61,17 +61,18 @@ pub(crate) fn slurs_vec_to_str(slurs: &[&str]) -> String { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod test { - use crate::utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str}; + use crate::{ + error::LemmyResult, + utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str}, + }; use pretty_assertions::assert_eq; use regex::RegexBuilder; #[test] - fn test_slur_filter() { - let slur_regex = Some(RegexBuilder::new(r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap()); + fn test_slur_filter() -> LemmyResult<()> { + let slur_regex = Some(RegexBuilder::new(r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build()?); let test = "faggot test kike tranny cocksucker retardeds. Capitalized Niggerz. This is a bunch of other safe text."; let slur_free = "No slurs here"; @@ -96,6 +97,8 @@ mod test { if let Err(slur_vec) = slur_check(test, &slur_regex) { assert_eq!(&slurs_vec_to_str(&slur_vec), has_slurs_err_str); } + + Ok(()) } // These helped with testing diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index c35eac7bf..98b9a2a25 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -1,4 +1,5 @@ use crate::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use clearurls::UrlCleaner; use itertools::Itertools; use regex::{Regex, RegexBuilder, RegexSet}; use std::sync::LazyLock; @@ -10,12 +11,8 @@ static VALID_MATRIX_ID_REGEX: LazyLock = LazyLock::new(|| { .expect("compile regex") }); // taken from https://en.wikipedia.org/wiki/UTM_parameters -static CLEAN_URL_PARAMS_REGEX: LazyLock = LazyLock::new(|| { - Regex::new( - r"^(utm_source|utm_medium|utm_campaign|utm_term|utm_content|gclid|gclsrc|dclid|fbclid)=", - ) - .expect("compile regex") -}); +static URL_CLEANER: LazyLock = + LazyLock::new(|| UrlCleaner::from_embedded_rules().expect("compile clearurls")); const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"]; const BODY_MAX_LENGTH: usize = 10000; @@ -257,16 +254,22 @@ pub fn build_and_check_regex(regex_str_opt: &Option<&str>) -> LemmyResult