diff --git a/.woodpecker.yml b/.woodpecker.yml index 4c9af5441..bca554733 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -143,16 +143,6 @@ steps: - diff tmp.schema crates/db_schema/src/schema.rs when: *slow_check_paths - check_diesel_migration_revertable: - image: willsquire/diesel-cli - environment: - CARGO_HOME: .cargo_home - DATABASE_URL: postgres://lemmy:password@database:5432/lemmy - commands: - - diesel migration run - - diesel migration redo - when: *slow_check_paths - check_db_perf_tool: image: *rust_image environment: @@ -194,6 +184,44 @@ steps: - cargo test --workspace --no-fail-fast when: *slow_check_paths + check_diesel_migration: + # TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server + image: *rust_image + environment: + LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy + RUST_BACKTRACE: "1" + CARGO_HOME: .cargo_home + DATABASE_URL: postgres://lemmy:password@database:5432/lemmy + PGUSER: lemmy + PGPASSWORD: password + PGHOST: database + PGDATABASE: lemmy + commands: + - cargo install diesel_cli + - export PATH="$CARGO_HOME/bin:$PATH" + # Run all migrations + - diesel migration run + # Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error) + - apt update && apt install -y lsb-release + - sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - + - apt update && apt install -y postgresql-client-16 + - psql -c "DROP SCHEMA IF EXISTS r CASCADE;" + - pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump + # Make sure that the newest migration is revertable without the `r` schema + - diesel migration redo + # Run schema setup twice, which fails on the 2nd time if `DROP SCHEMA IF EXISTS r CASCADE` drops the wrong things + - alias lemmy_schema_setup="target/lemmy_server --disable-scheduled-tasks --disable-http-server --disable-activity-sending" + - lemmy_schema_setup + - lemmy_schema_setup + # Make sure that the newest migration is revertable with the `r` schema + - diesel migration redo + # Check for changes in the schema, which would be caused by an incorrect migration + - psql -c "DROP SCHEMA IF EXISTS r CASCADE;" + - pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f after.sqldump + - diff before.sqldump after.sqldump + when: *slow_check_paths + run_federation_tests: image: node:20-bookworm-slim environment: diff --git a/Cargo.lock b/Cargo.lock index c0edf2dcf..61fcb5cd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772" [[package]] name = "activitypub_federation" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a028034c642d3ed16b535f98f48b3df30397833c183d68852d79de16650d5ed5" +checksum = "6e16130d5914e6483f99bde5a9bb97ca62e1f359e0b9791c8ebd5c7abd50fe8e" dependencies = [ "activitystreams-kinds", "actix-web", @@ -68,7 +68,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-sink", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "actix-form-data" -version = "0.7.0-beta.6" +version = "0.7.0-beta.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0588d156cb871d8c237d55ce398e2a65727370fb98352ba5b65c15a2f834b0f" +checksum = "6c2355a841a5d9a6c616d6a4f31336064116d206e6c1830de22730f983613a05" dependencies = [ "actix-multipart", "actix-web", @@ -123,7 +123,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "brotli", "bytes", "bytestring", @@ -157,7 +157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -315,7 +315,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -469,9 +469,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" dependencies = [ "backtrace", ] @@ -507,11 +507,13 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.8.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ - "event-listener", + "event-listener 4.0.3", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -533,18 +535,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "async-trait" -version = "0.1.78" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -568,9 +570,9 @@ checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" @@ -619,9 +621,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -736,9 +738,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -795,12 +797,6 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" -[[package]] -name = "bytecount" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" - [[package]] name = "bytemuck" version = "1.15.0" @@ -815,9 +811,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytestring" @@ -828,15 +824,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - [[package]] name = "captcha" version = "0.0.9" @@ -851,28 +838,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "cargo-platform" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - [[package]] name = "cc" version = "1.0.90" @@ -897,9 +862,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -932,9 +897,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -954,14 +919,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1028,6 +993,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.14.0" @@ -1303,7 +1277,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1336,7 +1310,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1472,11 +1446,11 @@ dependencies = [ [[package]] name = "diesel" -version = "2.1.5" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559" +checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "byteorder", "chrono", "diesel_derives", @@ -1512,18 +1486,18 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "diesel-derive-newtype" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ed4d69628c8de8eb4c3f50cddb0678cba3c5b4cbe3cb1067d4d6c62ca47e4e" +checksum = "d5adf688c584fe33726ce0e2898f608a2a92578ac94a4a92fcecf73214fe0716" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1535,7 +1509,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1565,7 +1539,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1647,11 +1621,11 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "memchr", ] @@ -1757,7 +1731,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -1801,19 +1775,36 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "event-listener" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ - "version_check", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] [[package]] name = "eyre" @@ -1852,9 +1843,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdeflate" @@ -1881,18 +1872,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin 0.9.8", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1999,7 +1978,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -2053,9 +2032,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -2078,9 +2057,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -2088,7 +2067,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.5", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2332,7 +2311,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.10", + "rustls 0.21.11", "tokio", "tokio-rustls 0.24.1", ] @@ -2379,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8215279f83f9b829403812f845aa2d0dd5966332aa2fd0334a375256f3dd0322" dependencies = [ "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -2469,9 +2448,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2522,15 +2501,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -2542,9 +2512,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2612,7 +2582,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lemmy_api" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2641,7 +2611,7 @@ dependencies = [ [[package]] name = "lemmy_api_common" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2679,7 +2649,7 @@ dependencies = [ [[package]] name = "lemmy_api_crud" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "accept-language", "activitypub_federation", @@ -2702,7 +2672,7 @@ dependencies = [ [[package]] name = "lemmy_apub" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2740,7 +2710,7 @@ dependencies = [ [[package]] name = "lemmy_db_perf" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "anyhow", "clap", @@ -2755,7 +2725,7 @@ dependencies = [ [[package]] name = "lemmy_db_schema" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "anyhow", @@ -2776,7 +2746,7 @@ dependencies = [ "once_cell", "pretty_assertions", "regex", - "rustls 0.21.10", + "rustls 0.21.11", "serde", "serde_json", "serde_with", @@ -2795,7 +2765,7 @@ dependencies = [ [[package]] name = "lemmy_db_views" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "actix-web", "chrono", @@ -2817,7 +2787,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_actor" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "chrono", "diesel", @@ -2837,7 +2807,7 @@ dependencies = [ [[package]] name = "lemmy_db_views_moderator" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "diesel", "diesel-async", @@ -2849,7 +2819,7 @@ dependencies = [ [[package]] name = "lemmy_federate" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "anyhow", @@ -2872,7 +2842,7 @@ dependencies = [ [[package]] name = "lemmy_routes" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "actix-web", @@ -2897,7 +2867,7 @@ dependencies = [ [[package]] name = "lemmy_server" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "activitypub_federation", "actix-cors", @@ -2940,7 +2910,7 @@ dependencies = [ [[package]] name = "lemmy_utils" -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" dependencies = [ "actix-web", "anyhow", @@ -2979,12 +2949,12 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357ff5edb6d8326473a64c82cf41ddf78ab116f89668c50c4fac1b321e5e80f4" +checksum = "47460276655930189e0919e4fbf46e46476b14f934f18a63dd726a5fb7b60e2e" dependencies = [ "async-trait", - "base64 0.21.7", + "base64 0.22.0", "chumsky", "email-encoding", "email_address", @@ -3192,9 +3162,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "metrics" @@ -3214,7 +3184,7 @@ checksum = "9bf4e7146e30ad172c42c39b3246864bd2d3c6396780711a1baf749cfe423e21" dependencies = [ "base64 0.21.7", "hyper", - "indexmap 2.2.5", + "indexmap 2.2.6", "ipnet", "metrics", "metrics-util", @@ -3305,21 +3275,21 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1911e88d5831f748a4097a43862d129e3c6fca831eecac9b8db6d01d93c9de2" +checksum = "9e0d88686dc561d743b40de8269b26eaf0dc58781bde087b0984646602021d08" dependencies = [ "async-lock", "async-trait", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", + "event-listener 5.3.0", "futures-util", "once_cell", "parking_lot 0.12.1", "quanta", "rustc_version", - "skeptic", "smallvec", "tagptr", "thiserror", @@ -3338,9 +3308,6 @@ name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] [[package]] name = "native-tls" @@ -3458,7 +3425,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -3475,7 +3442,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -3486,9 +3453,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -3687,6 +3654,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -3830,9 +3803,9 @@ dependencies = [ [[package]] name = "pict-rs" -version = "0.5.9" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e709cefc614ddbe5685b30298790a2fe8637523190c182c0215c18053ce7cfdf" +checksum = "7893e6e0d03847ff772f741b9b524544557274c56f2b0dad2a205260875b1d0f" dependencies = [ "actix-form-data", "actix-web", @@ -3849,7 +3822,6 @@ dependencies = [ "diesel", "diesel-async", "diesel-derive-enum", - "flume", "futures-core", "hex", "md-5", @@ -3864,7 +3836,7 @@ dependencies = [ "reqwest", "reqwest-middleware", "reqwest-tracing", - "rustls 0.22.2", + "rustls 0.22.3", "rustls-channel-resolver", "rustls-pemfile 2.1.1", "rusty-s3", @@ -3911,14 +3883,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3939,7 +3911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ "base64 0.21.7", - "indexmap 2.2.5", + "indexmap 2.2.6", "line-wrap", "quick-xml 0.31.0", "serde", @@ -4128,10 +4100,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -4167,22 +4139,11 @@ dependencies = [ "cc", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags 2.4.2", - "memchr", - "unicase", -] - [[package]] name = "quanta" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", @@ -4264,7 +4225,7 @@ version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", ] [[package]] @@ -4275,7 +4236,7 @@ checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -4338,19 +4299,19 @@ dependencies = [ "quote", "refinery-core", "regex", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4370,7 +4331,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -4381,15 +4342,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.26" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "async-compression", "base64 0.21.7", @@ -4412,7 +4373,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls 0.21.11", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4524,7 +4485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.4.2", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -4592,11 +4553,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", @@ -4617,9 +4578,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring 0.17.8", @@ -4629,9 +4590,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", "ring 0.17.8", @@ -4648,7 +4609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbd1941204442f051576a9a7ea8e8db074ad7fd43db1eb3378c3633f9f9e166" dependencies = [ "nanorand", - "rustls 0.22.2", + "rustls 0.22.3", ] [[package]] @@ -4672,9 +4633,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" [[package]] name = "rustls-webpki" @@ -4780,9 +4741,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -4793,9 +4754,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -4817,15 +4778,12 @@ name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" -dependencies = [ - "serde", -] [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -4841,22 +4799,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -4902,7 +4860,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -4919,7 +4877,7 @@ dependencies = [ "darling 0.20.8", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -4944,7 +4902,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -5036,21 +4994,6 @@ dependencies = [ "xml-builder", ] -[[package]] -name = "skeptic" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] - [[package]] name = "sketches-ddsketch" version = "0.2.2" @@ -5084,9 +5027,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smart-default" @@ -5096,7 +5039,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -5120,9 +5063,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -5228,7 +5168,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -5250,9 +5190,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -5278,7 +5218,7 @@ dependencies = [ "fnv", "once_cell", "plist", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "serde", "serde_derive", "serde_json", @@ -5331,7 +5271,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.52.0", ] @@ -5372,7 +5312,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -5439,9 +5379,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -5475,7 +5415,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -5522,7 +5462,7 @@ checksum = "dd5831152cb0d3f79ef5523b357319ba154795d64c7078b2daa95a803b54057f" dependencies = [ "futures", "ring 0.16.20", - "rustls 0.21.10", + "rustls 0.21.11", "tokio", "tokio-postgres", "tokio-rustls 0.24.1", @@ -5536,7 +5476,7 @@ checksum = "0ea13f22eda7127c827983bdaf0d7fff9df21c8817bab02815ac277a21143677" dependencies = [ "futures", "ring 0.17.8", - "rustls 0.22.2", + "rustls 0.22.3", "tokio", "tokio-postgres", "tokio-rustls 0.25.0", @@ -5560,7 +5500,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.11", "tokio", ] @@ -5570,7 +5510,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.2", + "rustls 0.22.3", "rustls-pki-types", "tokio", ] @@ -5621,7 +5561,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.8", + "toml_edit 0.22.9", ] [[package]] @@ -5639,7 +5579,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -5648,11 +5588,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -5856,7 +5796,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -6019,28 +5959,28 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", "termcolor", ] [[package]] name = "typed-builder" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -6226,7 +6166,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", "wasm-bindgen-shared", ] @@ -6260,7 +6200,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6719,7 +6659,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] @@ -6739,32 +6679,32 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.57", ] [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 554448d02..b8c029752 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.19.4-beta.2" +version = "0.19.4-beta.4" edition = "2021" description = "A link aggregator for the fediverse" license = "AGPL-3.0" @@ -88,24 +88,24 @@ unused_self = "deny" unwrap_used = "deny" [workspace.dependencies] -lemmy_api = { version = "=0.19.4-beta.2", path = "./crates/api" } -lemmy_api_crud = { version = "=0.19.4-beta.2", path = "./crates/api_crud" } -lemmy_apub = { version = "=0.19.4-beta.2", path = "./crates/apub" } -lemmy_utils = { version = "=0.19.4-beta.2", path = "./crates/utils", default-features = false } -lemmy_db_schema = { version = "=0.19.4-beta.2", path = "./crates/db_schema" } -lemmy_api_common = { version = "=0.19.4-beta.2", path = "./crates/api_common" } -lemmy_routes = { version = "=0.19.4-beta.2", path = "./crates/routes" } -lemmy_db_views = { version = "=0.19.4-beta.2", path = "./crates/db_views" } -lemmy_db_views_actor = { version = "=0.19.4-beta.2", path = "./crates/db_views_actor" } -lemmy_db_views_moderator = { version = "=0.19.4-beta.2", path = "./crates/db_views_moderator" } -lemmy_federate = { version = "=0.19.4-beta.2", path = "./crates/federate" } -activitypub_federation = { version = "0.5.2", default-features = false, features = [ +lemmy_api = { version = "=0.19.4-beta.4", path = "./crates/api" } +lemmy_api_crud = { version = "=0.19.4-beta.4", path = "./crates/api_crud" } +lemmy_apub = { version = "=0.19.4-beta.4", path = "./crates/apub" } +lemmy_utils = { version = "=0.19.4-beta.4", path = "./crates/utils", default-features = false } +lemmy_db_schema = { version = "=0.19.4-beta.4", path = "./crates/db_schema" } +lemmy_api_common = { version = "=0.19.4-beta.4", path = "./crates/api_common" } +lemmy_routes = { version = "=0.19.4-beta.4", path = "./crates/routes" } +lemmy_db_views = { version = "=0.19.4-beta.4", path = "./crates/db_views" } +lemmy_db_views_actor = { version = "=0.19.4-beta.4", path = "./crates/db_views_actor" } +lemmy_db_views_moderator = { version = "=0.19.4-beta.4", path = "./crates/db_views_moderator" } +lemmy_federate = { version = "=0.19.4-beta.4", path = "./crates/federate" } +activitypub_federation = { version = "0.5.4", default-features = false, features = [ "actix-web", ] } -diesel = "2.1.4" +diesel = "2.1.6" diesel_migrations = "2.1.0" diesel-async = "0.4.1" -serde = { version = "1.0.197", features = ["derive"] } +serde = { version = "1.0.198", features = ["derive"] } serde_with = "3.7.0" actix-web = { version = "4.5.1", default-features = false, features = [ "macros", @@ -121,28 +121,28 @@ tracing-error = "0.2.0" tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } url = { version = "2.5.0", features = ["serde"] } -reqwest = { version = "0.11.26", features = ["json", "blocking", "gzip"] } -reqwest-middleware = "0.2.4" -reqwest-tracing = "0.4.7" +reqwest = { version = "0.11.27", features = ["json", "blocking", "gzip"] } +reqwest-middleware = "0.2.5" +reqwest-tracing = "0.4.8" clokwerk = "0.4.0" doku = { version = "0.21.1", features = ["url-2"] } -bcrypt = "0.15.0" -chrono = { version = "0.4.35", features = ["serde"], default-features = false } -serde_json = { version = "1.0.114", features = ["preserve_order"] } -base64 = "0.21.7" -uuid = { version = "1.7.0", features = ["serde", "v4"] } -async-trait = "0.1.77" +bcrypt = "0.15.1" +chrono = { version = "0.4.38", features = ["serde"], default-features = false } +serde_json = { version = "1.0.116", features = ["preserve_order"] } +base64 = "0.22.0" +uuid = { version = "1.8.0", features = ["serde", "v4"] } +async-trait = "0.1.80" captcha = "0.0.9" -anyhow = { version = "1.0.81", features = [ +anyhow = { version = "1.0.82", features = [ "backtrace", ] } # backtrace is on by default on nightly, but not stable rust diesel_ltree = "0.3.1" -typed-builder = "0.18.1" +typed-builder = "0.18.2" serial_test = "2.0.0" -tokio = { version = "1.36.0", features = ["full"] } -regex = "1.10.3" +tokio = { version = "1.37.0", features = ["full"] } +regex = "1.10.4" once_cell = "1.19.0" -diesel-derive-newtype = "2.1.0" +diesel-derive-newtype = "2.1.2" diesel-derive-enum = { version = "2.1.0", features = ["postgres"] } strum = "0.25.0" strum_macros = "0.25.3" @@ -157,15 +157,15 @@ ts-rs = { version = "7.1.1", features = [ "chrono-impl", "no-serde-warnings", ] } -rustls = { version = "0.21.10", features = ["dangerous_configuration"] } +rustls = { version = "0.21.11", features = ["dangerous_configuration"] } futures-util = "0.3.30" tokio-postgres = "0.7.10" tokio-postgres-rustls = "0.10.0" urlencoding = "2.1.3" enum-map = "2.7" -moka = { version = "0.12.5", features = ["future"] } +moka = { version = "0.12.7", features = ["future"] } i-love-jesus = { version = "0.1.0" } -clap = { version = "4.5.2", features = ["derive"] } +clap = { version = "4.5.4", features = ["derive"] } pretty_assertions = "1.4.0" [dependencies] @@ -196,7 +196,7 @@ tracing-opentelemetry = { workspace = true, optional = true } opentelemetry = { workspace = true, optional = true } console-subscriber = { version = "0.1.10", optional = true } opentelemetry-otlp = { version = "0.12.0", optional = true } -pict-rs = { version = "0.5.9", optional = true } +pict-rs = { version = "0.5.13", optional = true } tokio.workspace = true actix-cors = "0.6.5" futures-util = { workspace = true } diff --git a/api_tests/package.json b/api_tests/package.json index 75ef362f8..9fbd3932b 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -6,6 +6,7 @@ "repository": "https://github.com/LemmyNet/lemmy", "author": "Dessalines", "license": "AGPL-3.0", + "packageManager": "pnpm@9.0.4", "scripts": { "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'", "fix": "prettier --write src && eslint --fix src", @@ -20,16 +21,16 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.11.27", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", + "@types/node": "^20.12.4", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", "download-file-sync": "^1.0.4", "eslint": "^8.57.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", - "lemmy-js-client": "0.19.4-alpha.13", + "lemmy-js-client": "0.19.4-alpha.18", "prettier": "^3.2.5", "ts-jest": "^29.1.0", - "typescript": "^5.4.2" + "typescript": "^5.4.4" } } diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 7b8c05328..8dc01c576 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -1,78 +1,1623 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -devDependencies: - '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 - '@types/node': - specifier: ^20.11.27 - version: 20.11.27 - '@typescript-eslint/eslint-plugin': - specifier: ^7.2.0 - version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/parser': - specifier: ^7.2.0 - version: 7.2.0(eslint@8.57.0)(typescript@5.4.2) - download-file-sync: - specifier: ^1.0.4 - version: 1.0.4 - eslint: - specifier: ^8.57.0 - version: 8.57.0 - eslint-plugin-prettier: - specifier: ^5.1.3 - version: 5.1.3(eslint@8.57.0)(prettier@3.2.5) - jest: - specifier: ^29.5.0 - version: 29.7.0(@types/node@20.11.27) - lemmy-js-client: - specifier: 0.19.4-alpha.13 - version: 0.19.4-alpha.13 - prettier: - specifier: ^3.2.5 - version: 3.2.5 - ts-jest: - specifier: ^29.1.0 - version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.2) - typescript: - specifier: ^5.4.2 - version: 5.4.2 +importers: + + .: + devDependencies: + '@types/jest': + specifier: ^29.5.12 + version: 29.5.12 + '@types/node': + specifier: ^20.12.4 + version: 20.12.7 + '@typescript-eslint/eslint-plugin': + specifier: ^7.5.0 + version: 7.5.0(@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.5.0 + version: 7.5.0(eslint@8.57.0)(typescript@5.4.5) + download-file-sync: + specifier: ^1.0.4 + version: 1.0.4 + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.1.3(eslint@8.57.0)(prettier@3.2.5) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.12.7) + lemmy-js-client: + specifier: 0.19.4-alpha.18 + version: 0.19.4-alpha.18 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + ts-jest: + specifier: ^29.1.0 + version: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.12.7))(typescript@5.4.5) + typescript: + specifier: ^5.4.4 + version: 5.4.5 packages: - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true + '@aashutoshrathi/word-wrap@1.2.6': + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + + '@ampproject/remapping@2.2.1': + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.23.5': + resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.23.5': + resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.23.9': + resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.23.6': + resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.23.6': + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.22.20': + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.23.0': + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.22.5': + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.22.15': + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.23.3': + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.22.5': + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.22.5': + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.22.6': + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.23.4': + resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.22.20': + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.23.9': + resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.23.4': + resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.23.9': + resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.23.3': + resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.23.3': + resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.23.9': + resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.23.9': + resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.23.9': + resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.0': + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.2': + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.3': + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.1': + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.1.2': + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.22': + resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.5': + resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@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/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@20.12.7': + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.32': + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + + '@typescript-eslint/eslint-plugin@7.5.0': + resolution: {integrity: sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.5.0': + resolution: {integrity: sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.5.0': + resolution: {integrity: sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.5.0': + resolution: {integrity: sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.5.0': + resolution: {integrity: sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.5.0': + resolution: {integrity: sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.5.0': + resolution: {integrity: sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@7.5.0': + resolution: {integrity: sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + 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'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.0.1: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + browserslist@4.22.3: + resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001581: + resolution: {integrity: sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.1: + resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + 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'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + download-file-sync@1.0.4: + resolution: {integrity: sha512-vH92qNH508jZZA12HQNq/aiMDfagr4JvjFiI17Bi8oYjsxwv5ZVIi7iHkYmUXxOQUr90tcVX+8EPePjAqG1Y0w==} + + electron-to-chromium@1.4.648: + resolution: {integrity: sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-prettier@5.1.3: + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + 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==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@2.1.0: + 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'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + 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'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.1: + resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + lemmy-js-client@0.19.4-alpha.18: + resolution: {integrity: sha512-CUKRIiINZF2zOfK5WzBDF071LjMmRBFHwiSYBMGJyQP1zu8sPKCb/ptg25WWrf79Y4uOaVLctgHg3oEUXmSUmQ==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + 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==} - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.0.4: + resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-jest@29.1.2: + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + update-browserslist-db@1.0.13: + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + v8-to-istanbul@9.2.0: + resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + engines: {node: '>=10.12.0'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@aashutoshrathi/word-wrap@1.2.6': {} + + '@ampproject/remapping@2.2.1': dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - dev: true - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} - engines: {node: '>=6.9.0'} + '@babel/code-frame@7.23.5': dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 - dev: true - /@babel/compat-data@7.23.5: - resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/compat-data@7.23.5': {} - /@babel/core@7.23.9: - resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==} - engines: {node: '>=6.9.0'} + '@babel/core@7.23.9': dependencies: '@ampproject/remapping': 2.2.1 '@babel/code-frame': 7.23.5 @@ -91,61 +1636,38 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /@babel/generator@7.23.6: - resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} - engines: {node: '>=6.9.0'} + '@babel/generator@7.23.6': dependencies: '@babel/types': 7.23.9 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 jsesc: 2.5.2 - dev: true - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.23.6': dependencies: '@babel/compat-data': 7.23.5 '@babel/helper-validator-option': 7.23.5 browserslist: 4.22.3 lru-cache: 5.1.1 semver: 6.3.1 - dev: true - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-environment-visitor@7.22.20': {} - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} + '@babel/helper-function-name@7.23.0': dependencies: '@babel/template': 7.23.9 '@babel/types': 7.23.9 - dev: true - - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.9 - dev: true - /@babel/helper-module-imports@7.22.15: - resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} - engines: {node: '>=6.9.0'} + '@babel/helper-hoist-variables@7.22.5': dependencies: '@babel/types': 7.23.9 - dev: true - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-module-imports@7.22.15': + dependencies: + '@babel/types': 7.23.9 + + '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-environment-visitor': 7.22.20 @@ -153,211 +1675,118 @@ packages: '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 - dev: true - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-plugin-utils@7.22.5': {} - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.22.5': dependencies: '@babel/types': 7.23.9 - dev: true - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.22.6': dependencies: '@babel/types': 7.23.9 - dev: true - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-string-parser@7.23.4': {} - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-validator-identifier@7.22.20': {} - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} - dev: true + '@babel/helper-validator-option@7.23.5': {} - /@babel/helpers@7.23.9: - resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} - engines: {node: '>=6.9.0'} + '@babel/helpers@7.23.9': dependencies: '@babel/template': 7.23.9 '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 transitivePeerDependencies: - supports-color - dev: true - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} - engines: {node: '>=6.9.0'} + '@babel/highlight@7.23.4': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true - /@babel/parser@7.23.9: - resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/parser@7.23.9': dependencies: '@babel/types': 7.23.9 - dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/template@7.23.9: - resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} - engines: {node: '>=6.9.0'} + '@babel/template@7.23.9': dependencies: '@babel/code-frame': 7.23.5 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - dev: true - /@babel/traverse@7.23.9: - resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} - engines: {node: '>=6.9.0'} + '@babel/traverse@7.23.9': dependencies: '@babel/code-frame': 7.23.5 '@babel/generator': 7.23.6 @@ -371,39 +1800,23 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true - /@babel/types@7.23.9: - resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} - engines: {node: '>=6.9.0'} + '@babel/types@7.23.9': dependencies: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: true - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true + '@bcoe/v8-coverage@0.2.3': {} - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true + '@eslint-community/regexpp@4.10.0': {} - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 debug: 4.3.4 @@ -416,83 +1829,55 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - dev: true - /@eslint/js@8.57.0: - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@eslint/js@8.57.0': {} - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true + '@humanwhocodes/module-importer@1.0.1': {} - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true + '@humanwhocodes/object-schema@2.0.2': {} - /@istanbuljs/load-nyc-config@1.1.0: - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 - dev: true - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - dev: true + '@istanbuljs/schema@0.1.3': {} - /@jest/console@29.7.0: - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - dev: true - /@jest/core@29.7.0: - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@jest/core@29.7.0': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.27) + jest-config: 29.7.0(@types/node@20.12.7) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -512,50 +1897,35 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /@jest/environment@29.7.0: - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 jest-mock: 29.7.0 - dev: true - /@jest/expect-utils@29.7.0: - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@29.7.0': dependencies: jest-get-type: 29.6.3 - dev: true - /@jest/expect@29.7.0: - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect@29.7.0': dependencies: expect: 29.7.0 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/fake-timers@29.7.0: - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.27 + '@types/node': 20.12.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 - dev: true - /@jest/globals@29.7.0: - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/globals@29.7.0': dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -563,16 +1933,8 @@ packages: jest-mock: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/reporters@29.7.0: - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@jest/reporters@29.7.0': dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 29.7.0 @@ -580,7 +1942,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.22 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -600,47 +1962,32 @@ packages: v8-to-istanbul: 9.2.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 - dev: true - /@jest/source-map@29.6.3: - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/source-map@29.6.3': dependencies: '@jridgewell/trace-mapping': 0.3.22 callsites: 3.1.0 graceful-fs: 4.2.11 - dev: true - /@jest/test-result@29.7.0: - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-result@29.7.0': dependencies: '@jest/console': 29.7.0 '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 - dev: true - /@jest/test-sequencer@29.7.0: - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-sequencer@29.7.0': dependencies: '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 - dev: true - /@jest/transform@29.7.0: - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/transform@29.7.0': dependencies: '@babel/core': 7.23.9 '@jest/types': 29.6.3 @@ -659,396 +2006,244 @@ packages: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color - dev: true - /@jest/types@29.6.3: - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@29.6.3': dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.11.27 + '@types/node': 20.12.7 '@types/yargs': 17.0.32 chalk: 4.1.2 - dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.3': dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.22 - dev: true - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/resolve-uri@3.1.1': {} - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true + '@jridgewell/set-array@1.1.2': {} - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true + '@jridgewell/sourcemap-codec@1.4.15': {} - /@jridgewell/trace-mapping@0.3.22: - resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + '@jridgewell/trace-mapping@0.3.22': dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - dev: true - /@pkgr/core@0.1.1: - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dev: true + '@pkgr/core@0.1.1': {} - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - dev: true + '@sinclair/typebox@0.27.8': {} - /@sinonjs/commons@3.0.1: - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 - dev: true - /@sinonjs/fake-timers@10.3.0: - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@sinonjs/fake-timers@10.3.0': dependencies: '@sinonjs/commons': 3.0.1 - dev: true - /@types/babel__core@7.20.5: - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.23.9 '@babel/types': 7.23.9 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 - dev: true - /@types/babel__generator@7.6.8: - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.6.8': dependencies: '@babel/types': 7.23.9 - dev: true - /@types/babel__template@7.4.4: - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - dev: true - /@types/babel__traverse@7.20.5: - resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + '@types/babel__traverse@7.20.5': dependencies: '@babel/types': 7.23.9 - dev: true - /@types/graceful-fs@4.1.9: - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.11.27 - dev: true + '@types/node': 20.12.7 - /@types/istanbul-lib-coverage@2.0.6: - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - dev: true + '@types/istanbul-lib-coverage@2.0.6': {} - /@types/istanbul-lib-report@3.0.3: - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + '@types/istanbul-lib-report@3.0.3': dependencies: '@types/istanbul-lib-coverage': 2.0.6 - dev: true - /@types/istanbul-reports@3.0.4: - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/istanbul-reports@3.0.4': dependencies: '@types/istanbul-lib-report': 3.0.3 - dev: true - /@types/jest@29.5.12: - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@types/jest@29.5.12': dependencies: expect: 29.7.0 pretty-format: 29.7.0 - dev: true - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true + '@types/json-schema@7.0.15': {} - /@types/node@20.11.27: - resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==} + '@types/node@20.12.7': dependencies: undici-types: 5.26.5 - dev: true - /@types/semver@7.5.8: - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - dev: true + '@types/semver@7.5.8': {} - /@types/stack-utils@2.0.3: - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - dev: true + '@types/stack-utils@2.0.3': {} - /@types/yargs-parser@21.0.3: - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - dev: true + '@types/yargs-parser@21.0.3': {} - /@types/yargs@17.0.32: - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@types/yargs@17.0.32': dependencies: '@types/yargs-parser': 21.0.3 - dev: true - /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) - '@typescript-eslint/visitor-keys': 7.2.0 + '@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.5.0 debug: 4.3.4 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) - '@typescript-eslint/visitor-keys': 7.2.0 + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.5.0 debug: 4.3.4 eslint: 8.57.0 - typescript: 5.4.2 + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@7.2.0: - resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@7.5.0': dependencies: - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/visitor-keys': 7.2.0 - dev: true + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/visitor-keys': 7.5.0 - /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2) + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@7.2.0: - resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true + '@typescript-eslint/types@7.5.0': {} - /@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2): - resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.5)': dependencies: - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/visitor-keys': 7.2.0 + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/visitor-keys': 7.5.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.2) - typescript: 5.4.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2): - resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 + '@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2) + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@7.2.0: - resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@7.5.0': dependencies: - '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/types': 7.5.0 eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: true + '@ungap/structured-clone@1.2.0': {} - /acorn-jsx@5.3.2(acorn@8.11.3): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 - dev: true - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true + acorn@8.11.3: {} - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - dev: true - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - dev: true - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true + ansi-regex@5.0.1: {} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 - dev: true - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - dev: true - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true + ansi-styles@5.2.0: {} - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: true - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - dev: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true + argparse@2.0.1: {} - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true + array-union@2.1.0: {} - /babel-jest@29.7.0(@babel/core@7.23.9): - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 + babel-jest@29.7.0(@babel/core@7.23.9): dependencies: '@babel/core': 7.23.9 '@jest/transform': 29.7.0 @@ -1060,11 +2255,8 @@ packages: slash: 3.0.0 transitivePeerDependencies: - supports-color - dev: true - /babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.22.5 '@istanbuljs/load-nyc-config': 1.1.0 @@ -1073,22 +2265,15 @@ packages: test-exclude: 6.0.0 transitivePeerDependencies: - supports-color - dev: true - /babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.23.9 '@babel/types': 7.23.9 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.5 - dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.9): - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 + babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.9): dependencies: '@babel/core': 7.23.9 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) @@ -1103,178 +2288,103 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.9) - dev: true - /babel-preset-jest@29.6.3(@babel/core@7.23.9): - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 + babel-preset-jest@29.6.3(@babel/core@7.23.9): dependencies: '@babel/core': 7.23.9 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.9) - dev: true - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true + balanced-match@1.0.2: {} - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 - dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + braces@3.0.2: dependencies: fill-range: 7.0.1 - dev: true - /browserslist@4.22.3: - resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + browserslist@4.22.3: dependencies: caniuse-lite: 1.0.30001581 electron-to-chromium: 1.4.648 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.3) - dev: true - /bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} + bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 - dev: true - /bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + bser@2.1.1: dependencies: node-int64: 0.4.0 - dev: true - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true + buffer-from@1.1.2: {} - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + callsites@3.1.0: {} - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true + camelcase@5.3.1: {} - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + camelcase@6.3.0: {} - /caniuse-lite@1.0.30001581: - resolution: {integrity: sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==} - dev: true + caniuse-lite@1.0.30001581: {} - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true - /char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - dev: true + char-regex@1.0.2: {} - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - dev: true + ci-info@3.9.0: {} - /cjs-module-lexer@1.2.3: - resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} - dev: true + cjs-module-lexer@1.2.3: {} - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: true + co@4.6.0: {} - /collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - dev: true + collect-v8-coverage@1.0.2: {} - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@1.9.3: dependencies: color-name: 1.1.3 - dev: true - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - dev: true - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true + color-name@1.1.3: {} - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true + color-name@1.1.4: {} - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + concat-map@0.0.1: {} - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - dev: true + convert-source-map@2.0.0: {} - /create-jest@29.7.0(@types/node@20.11.27): - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true + create-jest@29.7.0(@types/node@20.12.7): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.27) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -1282,151 +2392,70 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.3.4: dependencies: ms: 2.1.2 - dev: true - /dedent@1.5.1: - resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - dev: true + dedent@1.5.1: {} - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + deep-is@0.1.4: {} - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: true + deepmerge@4.3.1: {} - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - dev: true + detect-newline@3.1.0: {} - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + diff-sequences@29.6.3: {} - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - dev: true - /download-file-sync@1.0.4: - resolution: {integrity: sha512-vH92qNH508jZZA12HQNq/aiMDfagr4JvjFiI17Bi8oYjsxwv5ZVIi7iHkYmUXxOQUr90tcVX+8EPePjAqG1Y0w==} - dev: true + download-file-sync@1.0.4: {} - /electron-to-chromium@1.4.648: - resolution: {integrity: sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==} - dev: true + electron-to-chromium@1.4.648: {} - /emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - dev: true + emittery@0.13.1: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true + emoji-regex@8.0.0: {} - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - dev: true - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: true + escalade@3.1.1: {} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: true + escape-string-regexp@1.0.5: {} - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - dev: true + escape-string-regexp@2.0.0: {} - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true + escape-string-regexp@4.0.0: {} - /eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.2.5): - resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true + eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.2.5): dependencies: eslint: 8.57.0 prettier: 3.2.5 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 - dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - dev: true - /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} - dev: true + eslint-visitor-keys@3.4.3: {} - /eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + eslint@8.57.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 @@ -1468,50 +2497,28 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@9.6.1: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 - dev: true - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - dev: true + esprima@4.0.1: {} - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} + esquery@1.5.0: dependencies: estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - dev: true - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true + estraverse@5.3.0: {} - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true + esutils@2.0.3: {} - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -1522,158 +2529,91 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 - dev: true - /exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - dev: true + exit@0.1.2: {} - /expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 jest-get-type: 29.6.3 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 - dev: true - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true + fast-deep-equal@3.1.3: {} - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: true + fast-diff@1.3.0: {} - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true + fast-json-stable-stringify@2.1.0: {} - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true + fast-levenshtein@2.0.6: {} - /fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.17.1: dependencies: reusify: 1.0.4 - dev: true - /fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 - dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - dev: true - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 - dev: true - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@3.2.0: dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 - dev: true - /flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - dev: true + flatted@3.3.1: {} - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true + fsevents@2.3.3: optional: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true + function-bind@1.1.2: {} - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true + gensync@1.0.0-beta.2: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true + get-caller-file@2.0.5: {} - /get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - dev: true + get-package-type@0.1.0: {} - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: true + get-stream@6.0.1: {} - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - dev: true - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1681,23 +2621,14 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true + globals@11.12.0: {} - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - dev: true - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -1705,139 +2636,71 @@ packages: ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 - dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true + graceful-fs@4.2.11: {} - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + graphemer@1.4.0: {} - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: true + has-flag@3.0.0: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true + has-flag@4.0.0: {} - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} + hasown@2.0.0: dependencies: function-bind: 1.1.2 - dev: true - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - dev: true + html-escaper@2.0.2: {} - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - dev: true + human-signals@2.1.0: {} - /ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - dev: true + ignore@5.3.1: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true + import-local@3.1.0: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 - dev: true - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true + imurmurhash@0.1.4: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true + inherits@2.0.4: {} - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true + is-arrayish@0.2.1: {} - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.13.1: dependencies: hasown: 2.0.0 - dev: true - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true + is-extglob@2.1.1: {} - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - dev: true + is-fullwidth-code-point@3.0.0: {} - /is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - dev: true + is-generator-fn@2.1.0: {} - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true + is-number@7.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - dev: true + is-stream@2.0.1: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + isexe@2.0.0: {} - /istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - dev: true + istanbul-lib-coverage@3.2.2: {} - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} + istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.23.9 '@babel/parser': 7.23.9 @@ -1846,11 +2709,8 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - /istanbul-lib-instrument@6.0.1: - resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==} - engines: {node: '>=10'} + istanbul-lib-instrument@6.0.1: dependencies: '@babel/core': 7.23.9 '@babel/parser': 7.23.9 @@ -1859,54 +2719,39 @@ packages: semver: 7.5.4 transitivePeerDependencies: - supports-color - dev: true - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 - dev: true - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} + istanbul-lib-source-maps@4.0.1: dependencies: debug: 4.3.4 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color - dev: true - /istanbul-reports@3.1.6: - resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} - engines: {node: '>=8'} + istanbul-reports@3.1.6: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - dev: true - /jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 - dev: true - /jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-circus@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -1925,26 +2770,17 @@ packages: transitivePeerDependencies: - babel-plugin-macros - supports-color - dev: true - /jest-cli@29.7.0(@types/node@20.11.27): - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + jest-cli@29.7.0(@types/node@20.12.7): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.27) + create-jest: 29.7.0(@types/node@20.12.7) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.27) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -1953,24 +2789,12 @@ packages: - babel-plugin-macros - supports-color - ts-node - dev: true - /jest-config@29.7.0(@types/node@20.11.27): - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true + jest-config@29.7.0(@types/node@20.12.7): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 babel-jest: 29.7.0(@babel/core@7.23.9) chalk: 4.1.2 ci-info: 3.9.0 @@ -1990,63 +2814,47 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.12.7 transitivePeerDependencies: - babel-plugin-macros - supports-color - dev: true - /jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-diff@29.7.0: dependencies: chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-docblock@29.7.0: dependencies: detect-newline: 3.1.0 - dev: true - /jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-each@29.7.0: dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 - dev: true - /jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 jest-mock: 29.7.0 jest-util: 29.7.0 - dev: true - /jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + jest-get-type@29.6.3: {} - /jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-haste-map@29.7.0: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.11.27 + '@types/node': 20.12.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -2057,29 +2865,20 @@ packages: walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - dev: true - /jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-leak-detector@29.7.0: dependencies: jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@29.7.0: dependencies: chalk: 4.1.2 jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 - dev: true - /jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@29.7.0: dependencies: '@babel/code-frame': 7.23.5 '@jest/types': 29.6.3 @@ -2090,47 +2889,27 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 - dev: true - /jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 jest-util: 29.7.0 - dev: true - /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: jest-resolve: 29.7.0 - dev: true - /jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true + jest-regex-util@29.6.3: {} - /jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve-dependencies@29.7.0: dependencies: jest-regex-util: 29.6.3 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color - dev: true - /jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve@29.7.0: dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 @@ -2141,18 +2920,15 @@ packages: resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 - dev: true - /jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runner@29.7.0: dependencies: '@jest/console': 29.7.0 '@jest/environment': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2170,11 +2946,8 @@ packages: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - dev: true - /jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runtime@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 @@ -2183,7 +2956,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -2200,11 +2973,8 @@ packages: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - dev: true - /jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-snapshot@29.7.0: dependencies: '@babel/core': 7.23.9 '@babel/generator': 7.23.6 @@ -2228,23 +2998,17 @@ packages: semver: 7.5.4 transitivePeerDependencies: - supports-color - dev: true - /jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 - dev: true - /jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-validate@29.7.0: dependencies: '@jest/types': 29.6.3 camelcase: 6.3.0 @@ -2252,263 +3016,149 @@ packages: jest-get-type: 29.6.3 leven: 3.1.0 pretty-format: 29.7.0 - dev: true - /jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-watcher@29.7.0: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.27 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 - dev: true - /jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-worker@29.7.0: dependencies: - '@types/node': 20.11.27 + '@types/node': 20.12.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - dev: true - /jest@29.7.0(@types/node@20.11.27): - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + jest@29.7.0(@types/node@20.12.7): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.27) + jest-cli: 29.7.0(@types/node@20.12.7) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - dev: true - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true + js-tokens@4.0.0: {} - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true + js-yaml@3.14.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - dev: true - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true + jsesc@2.5.2: {} - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true + json-buffer@3.0.1: {} - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true + json-parse-even-better-errors@2.3.1: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-schema-traverse@0.4.1: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - dev: true + json5@2.2.3: {} - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - dev: true - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - dev: true + kleur@3.0.3: {} - /lemmy-js-client@0.19.4-alpha.13: - resolution: {integrity: sha512-ru1dCqPSfOJdsGq7am5J7P7f+/hpyHGhNbCEV/JAZP2U1lGHul32gLpBkilDnStDNdeq52scjKx+3WskRJFGFA==} - dev: true + lemmy-js-client@0.19.4-alpha.18: {} - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - dev: true + leven@3.1.0: {} - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true + lines-and-columns@1.2.4: {} - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 - dev: true - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - /lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: true + lodash.memoize@4.1.2: {} - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true + lodash.merge@4.6.2: {} - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 - dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 - dev: true - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + make-dir@4.0.0: dependencies: semver: 7.5.4 - dev: true - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true + make-error@1.3.6: {} - /makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + makeerror@1.0.12: dependencies: tmpl: 1.0.5 - dev: true - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + merge-stream@2.0.0: {} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true + merge2@1.4.1: {} - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + micromatch@4.0.5: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: true + mimic-fn@2.1.0: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - dev: true - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 - dev: true - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true + ms@2.1.2: {} - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true + natural-compare@1.4.0: {} - /node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - dev: true + node-int64@0.4.0: {} - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - dev: true + node-releases@2.0.14: {} - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true + normalize-path@3.0.0: {} - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 - dev: true - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - dev: true - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - dev: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 @@ -2516,541 +3166,285 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + p-limit@2.3.0: dependencies: p-try: 2.2.0 - dev: true - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - dev: true - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + p-locate@4.1.0: dependencies: p-limit: 2.3.0 - dev: true - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - dev: true + p-try@2.2.0: {} - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.23.5 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true + path-key@3.1.1: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true + path-parse@1.0.7: {} - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true + path-type@4.0.0: {} - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true + picocolors@1.0.0: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true + picomatch@2.3.1: {} - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - dev: true + pirates@4.0.6: {} - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 - dev: true - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true + prelude-ls@1.2.1: {} - /prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} + prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 - dev: true - /prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} - engines: {node: '>=14'} - hasBin: true - dev: true + prettier@3.2.5: {} - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.2.0 - dev: true - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /pure-rand@6.0.4: - resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} - dev: true + pure-rand@6.0.4: {} - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true + queue-microtask@1.2.3: {} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true + react-is@18.2.0: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - dev: true + require-directory@2.1.1: {} - /resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 - dev: true - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + resolve-from@4.0.0: {} - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + resolve-from@5.0.0: {} - /resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - dev: true + resolve.exports@2.0.2: {} - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + resolve@1.22.8: dependencies: is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true + reusify@1.0.4: {} - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: true - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - dev: true - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - dev: true + semver@6.3.1: {} - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true + semver@7.5.4: dependencies: lru-cache: 6.0.0 - dev: true - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true + semver@7.6.0: dependencies: lru-cache: 6.0.0 - dev: true - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - dev: true - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true + shebang-regex@3.0.0: {} - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true + signal-exit@3.0.7: {} - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true + sisteransi@1.0.5: {} - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + slash@3.0.0: {} - /source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: true + source-map@0.6.1: {} - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true + sprintf-js@1.0.3: {} - /stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 - dev: true - /string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} + string-length@4.0.2: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 - dev: true - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - dev: true - /strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - dev: true + strip-bom@4.0.0: {} - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - dev: true + strip-final-newline@2.0.0: {} - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 - dev: true - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - dev: true - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true + supports-preserve-symlinks-flag@1.0.0: {} - /synckit@0.8.8: - resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} - engines: {node: ^14.18.0 || >=16.0.0} + synckit@0.8.8: dependencies: '@pkgr/core': 0.1.1 tslib: 2.6.2 - dev: true - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 - dev: true - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - dev: true + tmpl@1.0.5: {} - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: true + to-fast-properties@2.0.0: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - dev: true - /ts-api-utils@1.3.0(typescript@5.4.2): - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: - typescript: 5.4.2 - dev: true + typescript: 5.4.5 - /ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.2): - resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} - engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true + ts-jest@29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.12.7))(typescript@5.4.5): dependencies: - '@babel/core': 7.23.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.27) + jest: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.5.4 - typescript: 5.4.2 + typescript: 5.4.5 yargs-parser: 21.1.1 - dev: true + optionalDependencies: + '@babel/core': 7.23.9 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.23.9) - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true + tslib@2.6.2: {} - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - dev: true + type-detect@4.0.8: {} - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true + type-fest@0.20.2: {} - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - dev: true + type-fest@0.21.3: {} - /typescript@5.4.2: - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} - engines: {node: '>=14.17'} - hasBin: true - dev: true + typescript@5.4.5: {} - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true + undici-types@5.26.5: {} - /update-browserslist-db@1.0.13(browserslist@4.22.3): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + update-browserslist-db@1.0.13(browserslist@4.22.3): dependencies: browserslist: 4.22.3 escalade: 3.1.1 picocolors: 1.0.0 - dev: true - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} - engines: {node: '>=10.12.0'} + v8-to-istanbul@9.2.0: dependencies: '@jridgewell/trace-mapping': 0.3.22 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - dev: true - /walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + walker@1.0.8: dependencies: makeerror: 1.0.12 - dev: true - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - dev: true - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true + wrappy@1.0.2: {} - /write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + write-file-atomic@4.0.2: dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 - dev: true - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - dev: true + y18n@5.0.8: {} - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true + yallist@3.1.1: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true + yallist@4.0.0: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - dev: true + yargs-parser@21.1.1: {} - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.1.1 @@ -3059,9 +3453,5 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yocto-queue@0.1.0: {} diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index de714f687..fc547481c 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -53,9 +53,7 @@ beforeAll(async () => { } }); -afterAll(() => { - unfollows(); -}); +afterAll(unfollows); function assertCommentFederation( commentOne?: CommentView, diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 02fac3d9a..5aa9fdfc8 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -31,10 +31,12 @@ import { searchPostLocal, longDelay, editCommunity, + unfollows, } from "./shared"; import { EditCommunity, EditSite } from "lemmy-js-client"; beforeAll(setupLogins); +afterAll(unfollows); function assertCommunityFederation( communityOne?: CommunityView, @@ -240,7 +242,7 @@ test("Admin actions in remote community are not federated to origin", async () = ); expect(banRes.banned).toBe(true); - // ban doesnt federate to community's origin instance alpha + // ban doesn't federate to community's origin instance alpha let alphaPost = (await resolvePost(alpha, gammaPost.post)).post; expect(alphaPost?.creator_banned_from_community).toBe(false); @@ -450,7 +452,7 @@ test("Dont receive community activities after unsubscribe", async () => { ); expect(communityRes1.community_view.counts.subscribers).toBe(2); - // temporarily block alpha, so that it doesnt know about unfollow + // temporarily block alpha, so that it doesn't know about unfollow let editSiteForm: EditSite = {}; editSiteForm.allowed_instances = ["lemmy-epsilon"]; await beta.editSite(editSiteForm); @@ -511,7 +513,7 @@ test("Fetch community, includes posts", async () => { expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id); }); -test("Content in local-only community doesnt federate", async () => { +test("Content in local-only community doesn't federate", async () => { // create a community and set it local-only let communityRes = (await createCommunity(alpha)).community_view.community; let form: EditCommunity = { diff --git a/api_tests/src/follow.spec.ts b/api_tests/src/follow.spec.ts index 276213eac..161c7f045 100644 --- a/api_tests/src/follow.spec.ts +++ b/api_tests/src/follow.spec.ts @@ -15,9 +15,7 @@ import { beforeAll(setupLogins); -afterAll(() => { - unfollows(); -}); +afterAll(unfollows); test("Follow local community", async () => { let user = await registerUser(beta, betaUrl); diff --git a/api_tests/src/image.spec.ts b/api_tests/src/image.spec.ts index 3a82b572d..a1b3c3f3e 100644 --- a/api_tests/src/image.spec.ts +++ b/api_tests/src/image.spec.ts @@ -15,7 +15,6 @@ import { createCommunity, createPost, deleteAllImages, - delta, epsilon, followCommunity, gamma, @@ -28,20 +27,22 @@ import { setupLogins, waitForPost, unfollows, + getPost, + waitUntil, + randomString, + createPostWithThumbnail, } from "./shared"; const downloadFileSync = require("download-file-sync"); beforeAll(setupLogins); -afterAll(() => { - unfollows(); -}); +afterAll(unfollows); test("Upload image and delete it", async () => { // Before running this test, you need to delete all previous images in the DB await deleteAllImages(alpha); - // Upload test image. We use a simple string buffer as pictrs doesnt require an actual image + // Upload test image. We use a simple string buffer as pictrs doesn't require an actual image // in testing mode. const upload_form: UploadImage = { image: Buffer.from("test"), @@ -71,9 +72,14 @@ test("Upload image and delete it", async () => { // The deleteUrl is a combination of the endpoint, delete token, and alias let firstImage = listMediaRes.images[0]; - let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`; + let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.local_image.pictrs_delete_token}/${firstImage.local_image.pictrs_alias}`; expect(deleteUrl).toBe(upload.delete_url); + // Make sure the uploader is correct + expect(firstImage.person.actor_id).toBe( + `http://lemmy-alpha:8541/u/lemmy_alpha`, + ); + // delete image const delete_form: DeleteImage = { token: upload.files![0].delete_token, @@ -230,7 +236,7 @@ test("No image proxying if setting is disabled", async () => { ); expect(post.post_view.post).toBeDefined(); - // remote image doesnt get proxied after upload + // remote image doesn't get proxied after upload expect( post.post_view.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"), ).toBeTruthy(); @@ -243,7 +249,7 @@ test("No image proxying if setting is disabled", async () => { ); expect(betaPost.post).toBeDefined(); - // remote image doesnt get proxied after federation + // remote image doesn't get proxied after federation expect( betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"), ).toBeTruthy(); @@ -252,3 +258,59 @@ test("No image proxying if setting is disabled", async () => { // Make sure the alt text got federated expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text); }); + +test("Make regular post, and give it a custom thumbnail", async () => { + const uploadForm1: UploadImage = { + image: Buffer.from("testRegular1"), + }; + const upload1 = await alphaImage.uploadImage(uploadForm1); + + const community = await createCommunity(alphaImage); + + // Use wikipedia since it has an opengraph image + const wikipediaUrl = "https://wikipedia.org/"; + + let post = await createPostWithThumbnail( + alphaImage, + community.community_view.community.id, + wikipediaUrl, + upload1.url!, + ); + + // Wait for the metadata to get fetched, since this is backgrounded now + post = await waitUntil( + () => getPost(alphaImage, post.post_view.post.id), + p => p.post_view.post.thumbnail_url != undefined, + ); + expect(post.post_view.post.url).toBe(wikipediaUrl); + // Make sure it uses custom thumbnail + expect(post.post_view.post.thumbnail_url).toBe(upload1.url); +}); + +test("Create an image post, and make sure a custom thumbnail doesn't overwrite it", async () => { + const uploadForm1: UploadImage = { + image: Buffer.from("test1"), + }; + const upload1 = await alphaImage.uploadImage(uploadForm1); + + const uploadForm2: UploadImage = { + image: Buffer.from("test2"), + }; + const upload2 = await alphaImage.uploadImage(uploadForm2); + + const community = await createCommunity(alphaImage); + + let post = await createPostWithThumbnail( + alphaImage, + community.community_view.community.id, + upload1.url!, + upload2.url!, + ); + post = await waitUntil( + () => getPost(alphaImage, post.post_view.post.id), + p => p.post_view.post.thumbnail_url != undefined, + ); + expect(post.post_view.post.url).toBe(upload1.url); + // Make sure the custom thumbnail is ignored + expect(post.post_view.post.thumbnail_url == upload2.url).toBe(false); +}); diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index c1640e93e..b2ab50222 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -51,9 +51,7 @@ beforeAll(async () => { await unfollows(); }); -afterAll(() => { - unfollows(); -}); +afterAll(unfollows); async function assertPostFederation(postOne: PostView, postTwo: PostView) { // Link metadata is generated in background task and may not be ready yet at this time, @@ -745,3 +743,23 @@ test("Block post that contains banned URL", async () => { editSiteForm.blocked_urls = []; await epsilon.editSite(editSiteForm); }); + +test("Fetch post with redirect", async () => { + let alphaPost = await createPost(alpha, betaCommunity!.community.id); + expect(alphaPost.post_view.post).toBeDefined(); + + // beta fetches from alpha as usual + let betaPost = await resolvePost(beta, alphaPost.post_view.post); + expect(betaPost.post).toBeDefined(); + + // gamma fetches from beta, and gets redirected to alpha + let gammaPost = await resolvePost(gamma, betaPost.post!.post); + expect(gammaPost.post).toBeDefined(); + + // fetch remote object from local url, which redirects to the original url + let form: ResolveObject = { + q: `http://lemmy-gamma:8561/post/${gammaPost.post!.post.id}`, + }; + let gammaPost2 = await gamma.resolveObject(form); + expect(gammaPost2.post).toBeDefined(); +}); diff --git a/api_tests/src/private_message.spec.ts b/api_tests/src/private_message.spec.ts index 063ee05ee..8fd683ff0 100644 --- a/api_tests/src/private_message.spec.ts +++ b/api_tests/src/private_message.spec.ts @@ -21,9 +21,7 @@ beforeAll(async () => { recipient_id = 3; }); -afterAll(() => { - unfollows(); -}); +afterAll(unfollows); test("Create a private message", async () => { let pmRes = await createPrivateMessage(alpha, recipient_id); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 723b63887..1a8a9afaf 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -203,6 +203,7 @@ export async function createPost( // use example.com for consistent title and embed description name: string = randomString(5), alt_text = randomString(10), + custom_thumbnail: string | undefined = undefined, ): Promise { let form: CreatePost = { name, @@ -210,6 +211,7 @@ export async function createPost( body, alt_text, community_id, + custom_thumbnail, }; return api.createPost(form); } @@ -226,6 +228,21 @@ export async function editPost( return api.editPost(form); } +export async function createPostWithThumbnail( + api: LemmyHttp, + community_id: number, + url: string, + custom_thumbnail: string, +): Promise { + let form: CreatePost = { + name: randomString(10), + url, + community_id, + custom_thumbnail, + }; + return api.createPost(form); +} + export async function deletePost( api: LemmyHttp, deleted: boolean, @@ -875,8 +892,8 @@ export async function deleteAllImages(api: LemmyHttp) { for (const image of imagesRes.images) { const form: DeleteImage = { - token: image.pictrs_delete_token, - filename: image.pictrs_alias, + token: image.local_image.pictrs_delete_token, + filename: image.local_image.pictrs_alias, }; await api.deleteImage(form); } diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index 5af054918..f44f3cc0a 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -20,11 +20,13 @@ import { getComments, fetchFunction, alphaImage, + unfollows, } from "./shared"; import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; beforeAll(setupLogins); +afterAll(unfollows); let apShortname: string; diff --git a/config/defaults.hjson b/config/defaults.hjson index c52f9055e..1fbab1fb9 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -49,7 +49,7 @@ cache_external_link_previews: true # Specifies how to handle remote images, so that users don't have to connect directly to remote servers. image_mode: - # Leave images unchanged, don't generate any local thumbnails for post urls. Instead the the + # Leave images unchanged, don't generate any local thumbnails for post urls. Instead the # Opengraph image is directly returned as thumbnail "None" diff --git a/crates/api/src/comment/distinguish.rs b/crates/api/src/comment/distinguish.rs index a346bf4ca..dfd850e89 100644 --- a/crates/api/src/comment/distinguish.rs +++ b/crates/api/src/comment/distinguish.rs @@ -9,15 +9,17 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn distinguish_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?; +) -> LemmyResult> { + let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; check_community_user_action( &local_user_view.person, @@ -54,7 +56,8 @@ pub async fn distinguish_comment( data.comment_id, Some(local_user_view.person.id), ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; Ok(Json(CommentResponse { comment_view, diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index e15d74422..d0aa4a6c2 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ traits::Likeable, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use std::ops::Deref; #[tracing::instrument(skip(context))] @@ -25,7 +25,7 @@ pub async fn like_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let mut recipient_ids = Vec::::new(); @@ -35,7 +35,9 @@ pub async fn like_comment( check_bot_account(&local_user_view.person)?; let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; check_community_user_action( &local_user_view.person, @@ -46,9 +48,10 @@ pub async fn like_comment( // Add parent poster or commenter to recipients let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await; - if let Ok(reply) = comment_reply { + if let Ok(Some(reply)) = comment_reply { let recipient_id = reply.recipient_id; - if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await + if let Ok(Some(local_recipient)) = + LocalUserView::read_person(&mut context.pool(), recipient_id).await { recipient_ids.push(local_recipient.local_user.id); } diff --git a/crates/api/src/comment/list_comment_likes.rs b/crates/api/src/comment/list_comment_likes.rs index b442cce23..8c2c9dd32 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; /// Lists likes for a comment #[tracing::instrument(skip(context))] @@ -13,13 +13,15 @@ pub async fn list_comment_likes( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let comment_view = CommentView::read( &mut context.pool(), data.comment_id, Some(local_user_view.person.id), ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; + is_mod_or_admin( &mut context.pool(), &local_user_view.person, diff --git a/crates/api/src/comment/save.rs b/crates/api/src/comment/save.rs index 95c08e701..f9d649e48 100644 --- a/crates/api/src/comment/save.rs +++ b/crates/api/src/comment/save.rs @@ -8,14 +8,14 @@ use lemmy_db_schema::{ traits::Saveable, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn save_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let comment_saved_form = CommentSavedForm { comment_id: data.comment_id, person_id: local_user_view.person.id, @@ -33,7 +33,9 @@ pub async fn save_comment( let comment_id = data.comment_id; let person_id = local_user_view.person.id; - let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id)).await?; + let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id)) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; Ok(Json(CommentResponse { comment_view, diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index f8075460f..c008d1df2 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ traits::Reportable, }; use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; /// Creates a comment report and notifies the moderators of the community #[tracing::instrument(skip(context))] @@ -27,7 +27,7 @@ pub async fn create_comment_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let reason = data.reason.trim().to_string(); @@ -35,7 +35,9 @@ pub async fn create_comment_report( let person_id = local_user_view.person.id; let comment_id = data.comment_id; - let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?; + let comment_view = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; check_community_user_action( &local_user_view.person, @@ -58,8 +60,9 @@ 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?; + let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommentReport)?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/comment_report/list.rs b/crates/api/src/comment_report/list.rs index f1577a77f..d2f723819 100644 --- a/crates/api/src/comment_report/list.rs +++ b/crates/api/src/comment_report/list.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ utils::check_community_mod_of_any_or_admin_action, }; use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; /// Lists comment reports for a community if an id is supplied /// or returns all comment reports for communities a user moderates @@ -14,7 +14,7 @@ pub async fn list_comment_reports( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let community_id = data.community_id; let comment_id = data.comment_id; let unresolved_only = data.unresolved_only.unwrap_or_default(); diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index 70e7d89f6..40aad9569 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable}; use lemmy_db_views::structs::{CommentReportView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; /// Resolves or unresolves a comment report and notifies the moderators of the community #[tracing::instrument(skip(context))] @@ -14,10 +14,12 @@ pub async fn resolve_comment_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> 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?; + let report = CommentReportView::read(&mut context.pool(), report_id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommentReport)?; let person_id = local_user_view.person.id; check_community_mod_action( @@ -39,8 +41,9 @@ 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?; + let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommentReport)?; 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 69692e58c..df6b3fbe4 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -15,14 +15,14 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn add_mod_to_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let community_id = data.community_id; // Verify that only mods or admins can add mod @@ -33,7 +33,9 @@ pub async fn add_mod_to_community( &mut context.pool(), ) .await?; - let community = Community::read(&mut context.pool(), community_id).await?; + let community = Community::read(&mut context.pool(), community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if local_user_view.local_user.admin && !community.local { Err(LemmyErrorType::NotAModerator)? } diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 91cb8884f..93cf00415 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -21,7 +21,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::validation::is_valid_body_field, }; @@ -30,7 +30,7 @@ pub async fn ban_from_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false); let expires = check_expire_time(data.expires)?; @@ -89,7 +89,9 @@ 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?; + let person_view = PersonView::read(&mut context.pool(), data.person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; ActivityChannel::submit_activity( SendActivityData::BanFromCommunity { diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index fd4a5a01b..449addf32 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -14,14 +14,14 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn block_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let community_id = data.community_id; let person_id = local_user_view.person.id; let community_block_form = CommunityBlockForm { @@ -51,7 +51,9 @@ pub async fn block_community( } let community_view = - CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?; + CommunityView::read(&mut context.pool(), community_id, Some(person_id), false) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; ActivityChannel::submit_activity( SendActivityData::FollowCommunity( diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index bb7b80f00..853cfde14 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -15,15 +15,17 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn follow_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let community = Community::read(&mut context.pool(), data.community_id).await?; +) -> LemmyResult> { + let community = Community::read(&mut context.pool(), data.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; let mut community_follower_form = CommunityFollowerForm { community_id: community.id, person_id: local_user_view.person.id, @@ -62,7 +64,10 @@ pub async fn follow_community( let community_id = data.community_id; let person_id = local_user_view.person.id; let community_view = - CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?; + CommunityView::read(&mut context.pool(), community_id, Some(person_id), false) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; + let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; Ok(Json(CommunityResponse { diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index 27919a42b..997d88de3 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -15,14 +15,14 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn hide_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Verify its a admin (only admin can hide or unhide it) is_admin(&local_user_view)?; diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index 340bb6b63..5f3a6032e 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, location_info, }; @@ -26,7 +26,7 @@ pub async fn transfer_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let community_id = data.community_id; let mut community_mods = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; @@ -79,8 +79,8 @@ pub async fn transfer_community( let person_id = local_user_view.person.id; let community_view = CommunityView::read(&mut context.pool(), community_id, Some(person_id), false) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; let community_id = data.community_id; let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index d65ae0a28..4eee772c9 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -26,7 +26,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}, + error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}, utils::slurs::check_slurs, }; use std::io::Cursor; @@ -44,7 +44,7 @@ pub mod site; pub mod sitemap; /// Converts the captcha to a base64 encoded wav audio file -pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result { +pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult { let letters = captcha.as_wav(); // Decode each wav file, concatenate the samples @@ -78,7 +78,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result Result<(), LemmyError> { +pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> LemmyResult<()> { let slur_regex = &local_site_to_slur_regex(local_site); check_slurs(reason, slur_regex)?; @@ -91,7 +91,7 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul } } -pub fn read_auth_token(req: &HttpRequest) -> Result, LemmyError> { +pub fn read_auth_token(req: &HttpRequest) -> LemmyResult> { // Try reading jwt from auth header if let Ok(header) = Authorization::::parse(req) { Ok(Some(header.as_ref().token().to_string())) @@ -135,7 +135,7 @@ pub(crate) fn generate_totp_2fa_secret() -> String { Secret::generate_secret().to_string() } -fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> Result { +fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult { let sec = Secret::Raw(secret.as_bytes().to_vec()); let sec_bytes = sec .to_bytes() @@ -248,11 +248,13 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities( pub async fn local_user_view_from_jwt( jwt: &str, context: &LemmyContext, -) -> Result { +) -> LemmyResult { 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?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; check_user_valid(&local_user_view.person)?; Ok(local_user_view) diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs index 502335876..cd827454e 100644 --- a/crates/api/src/local_user/add_admin.rs +++ b/crates/api/src/local_user/add_admin.rs @@ -13,21 +13,21 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn add_admin( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Make sure user is an admin is_admin(&local_user_view)?; // Make sure that the person_id added is local let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id) - .await - .with_lemmy_type(LemmyErrorType::ObjectNotLocal)?; + .await? + .ok_or(LemmyErrorType::ObjectNotLocal)?; let added_admin = 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 f039c9a0c..c31940fba 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -18,7 +18,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::validation::is_valid_body_field, }; @@ -27,7 +27,7 @@ pub async fn ban_from_site( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Make sure user is an admin is_admin(&local_user_view)?; @@ -49,7 +49,7 @@ 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(local_user) = local_user { + if let Ok(Some(local_user)) = local_user { LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?; } @@ -70,7 +70,9 @@ pub async fn ban_from_site( ModBan::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), person.id).await?; + let person_view = PersonView::read(&mut context.pool(), person.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; ban_nonlocal_user_from_local_communities( &local_user_view, diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index cb345616b..698703a9b 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -9,14 +9,14 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn block_person( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let target_id = data.person_id; let person_id = local_user_view.person.id; @@ -30,8 +30,12 @@ pub async fn block_person( target_id, }; - let target_user = LocalUserView::read_person(&mut context.pool(), target_id).await; - if target_user.map(|t| t.local_user.admin) == Ok(true) { + let target_user = LocalUserView::read_person(&mut context.pool(), target_id) + .await + .ok() + .flatten(); + + if target_user.is_some_and(|t| t.local_user.admin) { Err(LemmyErrorType::CantBlockAdmin)? } @@ -45,7 +49,9 @@ pub async fn block_person( .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } - let person_view = PersonView::read(&mut context.pool(), target_id).await?; + let person_view = PersonView::read(&mut context.pool(), target_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; 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 ab5b32dd9..50ee10bb6 100644 --- a/crates/api/src/local_user/change_password.rs +++ b/crates/api/src/local_user/change_password.rs @@ -11,7 +11,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn change_password( @@ -19,7 +19,7 @@ pub async fn change_password( req: HttpRequest, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { password_length_check(&data.new_password)?; // Make sure passwords match 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 50a267d6a..9d693a750 100644 --- a/crates/api/src/local_user/change_password_after_reset.rs +++ b/crates/api/src/local_user/change_password_after_reset.rs @@ -10,18 +10,19 @@ use lemmy_db_schema::source::{ login_token::LoginToken, password_reset_request::PasswordResetRequest, }; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn change_password_after_reset( data: Json, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { // Fetch the user_id from the token let token = data.token.clone(); let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token) - .await - .map(|p| p.local_user_id)?; + .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 342a90a78..e8bb0284c 100644 --- a/crates/api/src/local_user/generate_totp_secret.rs +++ b/crates/api/src/local_user/generate_totp_secret.rs @@ -8,7 +8,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; /// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp] /// to enable it. This can only be called if 2FA is currently disabled. @@ -16,8 +16,10 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType}; pub async fn generate_totp_secret( local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; if local_user_view.local_user.totp_2fa_enabled { return Err(LemmyErrorType::TotpAlreadyEnabled)?; diff --git a/crates/api/src/local_user/get_captcha.rs b/crates/api/src/local_user/get_captcha.rs index 3d93a793c..ac64fa07c 100644 --- a/crates/api/src/local_user/get_captcha.rs +++ b/crates/api/src/local_user/get_captcha.rs @@ -17,10 +17,10 @@ use lemmy_db_schema::source::{ captcha_answer::{CaptchaAnswer, CaptchaAnswerForm}, local_site::LocalSite, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] -pub async fn get_captcha(context: Data) -> Result { +pub async fn get_captcha(context: Data) -> LemmyResult { let local_site = LocalSite::read(&mut context.pool()).await?; let mut res = HttpResponseBuilder::new(StatusCode::OK); res.insert_header(CacheControl(vec![CacheDirective::NoStore])); diff --git a/crates/api/src/local_user/list_banned.rs b/crates/api/src/local_user/list_banned.rs index 5c76d89a8..ba2c0d403 100644 --- a/crates/api/src/local_user/list_banned.rs +++ b/crates/api/src/local_user/list_banned.rs @@ -2,12 +2,12 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin}; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub async fn list_banned_users( context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api/src/local_user/list_logins.rs b/crates/api/src/local_user/list_logins.rs index f1ae76be5..013236dcd 100644 --- a/crates/api/src/local_user/list_logins.rs +++ b/crates/api/src/local_user/list_logins.rs @@ -2,12 +2,12 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::login_token::LoginToken; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub async fn list_logins( context: Data, local_user_view: LocalUserView, -) -> Result>, LemmyError> { +) -> LemmyResult>> { let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?; Ok(Json(logins)) diff --git a/crates/api/src/local_user/list_media.rs b/crates/api/src/local_user/list_media.rs index 25df8a4c2..779558dab 100644 --- a/crates/api/src/local_user/list_media.rs +++ b/crates/api/src/local_user/list_media.rs @@ -3,19 +3,18 @@ use lemmy_api_common::{ context::LemmyContext, person::{ListMedia, ListMediaResponse}, }; -use lemmy_db_schema::source::images::LocalImage; -use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_db_views::structs::{LocalImageView, LocalUserView}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_media( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let page = data.page; let limit = data.limit; - let images = LocalImage::get_all_paged_by_local_user_id( + let images = LocalImageView::get_all_paged_by_local_user_id( &mut context.pool(), local_user_view.local_user.id, page, diff --git a/crates/api/src/local_user/login.rs b/crates/api/src/local_user/login.rs index 4eae762be..19f84f703 100644 --- a/crates/api/src/local_user/login.rs +++ b/crates/api/src/local_user/login.rs @@ -16,22 +16,24 @@ use lemmy_db_schema::{ RegistrationMode, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn login( data: Json, req: HttpRequest, context: Data, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; // 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 - .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; + .await? + .ok_or(LemmyErrorType::IncorrectLogin)?; // Verify the password let valid: bool = verify( @@ -70,7 +72,7 @@ async fn check_registration_application( local_user_view: &LocalUserView, local_site: &LocalSite, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { if (local_site.registration_mode == RegistrationMode::RequireApplication || local_site.registration_mode == RegistrationMode::Closed) && !local_user_view.local_user.accepted_application @@ -79,7 +81,9 @@ async fn check_registration_application( // 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?; + 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 { diff --git a/crates/api/src/local_user/notifications/list_mentions.rs b/crates/api/src/local_user/notifications/list_mentions.rs index 9f9ee3ae8..bf3cd8e0d 100644 --- a/crates/api/src/local_user/notifications/list_mentions.rs +++ b/crates/api/src/local_user/notifications/list_mentions.rs @@ -5,14 +5,14 @@ use lemmy_api_common::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::person_mention_view::PersonMentionQuery; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_mentions( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let sort = data.sort; let page = data.page; let limit = data.limit; diff --git a/crates/api/src/local_user/notifications/list_replies.rs b/crates/api/src/local_user/notifications/list_replies.rs index 555989721..d88595d96 100644 --- a/crates/api/src/local_user/notifications/list_replies.rs +++ b/crates/api/src/local_user/notifications/list_replies.rs @@ -5,14 +5,14 @@ use lemmy_api_common::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_replies( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let sort = data.sort; let page = data.page; let limit = data.limit; diff --git a/crates/api/src/local_user/notifications/mark_all_read.rs b/crates/api/src/local_user/notifications/mark_all_read.rs index d3667460b..558d276f7 100644 --- a/crates/api/src/local_user/notifications/mark_all_read.rs +++ b/crates/api/src/local_user/notifications/mark_all_read.rs @@ -6,13 +6,13 @@ use lemmy_db_schema::source::{ private_message::PrivateMessage, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn mark_all_notifications_read( context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_id = local_user_view.person.id; // Mark all comment_replies as read 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 4cce598ac..90c8efb6e 100644 --- a/crates/api/src/local_user/notifications/mark_mention_read.rs +++ b/crates/api/src/local_user/notifications/mark_mention_read.rs @@ -9,16 +9,18 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::PersonMentionView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn mark_person_mention_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_mention_id = data.person_mention_id; - let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?; + let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPersonMention)?; if local_user_view.person.id != read_person_mention.recipient_id { Err(LemmyErrorType::CouldntUpdateComment)? @@ -37,7 +39,9 @@ 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?; + PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)) + .await? + .ok_or(LemmyErrorType::CouldntFindPersonMention)?; 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 f7b259c94..fdcfa5727 100644 --- a/crates/api/src/local_user/notifications/mark_reply_read.rs +++ b/crates/api/src/local_user/notifications/mark_reply_read.rs @@ -9,16 +9,18 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommentReplyView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn mark_reply_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let comment_reply_id = data.comment_reply_id; - let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?; + let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommentReply)?; if local_user_view.person.id != read_comment_reply.recipient_id { Err(LemmyErrorType::CouldntUpdateComment)? @@ -38,7 +40,9 @@ 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?; + CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)) + .await? + .ok_or(LemmyErrorType::CouldntFindCommentReply)?; Ok(Json(CommentReplyResponse { comment_reply_view })) } diff --git a/crates/api/src/local_user/notifications/unread_count.rs b/crates/api/src/local_user/notifications/unread_count.rs index c0b1f0f2e..9d06f7c62 100644 --- a/crates/api/src/local_user/notifications/unread_count.rs +++ b/crates/api/src/local_user/notifications/unread_count.rs @@ -2,13 +2,13 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse}; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn unread_count( context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_id = local_user_view.person.id; let replies = CommentReplyView::get_unread_replies(&mut context.pool(), person_id).await?; diff --git a/crates/api/src/local_user/report_count.rs b/crates/api/src/local_user/report_count.rs index 3352f64eb..32448dcaa 100644 --- a/crates/api/src/local_user/report_count.rs +++ b/crates/api/src/local_user/report_count.rs @@ -10,14 +10,14 @@ use lemmy_db_views::structs::{ PostReportView, PrivateMessageReportView, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn report_count( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_id = local_user_view.person.id; let admin = local_user_view.local_user.admin; let community_id = data.community_id; diff --git a/crates/api/src/local_user/reset_password.rs b/crates/api/src/local_user/reset_password.rs index 414f506ba..edb5adee6 100644 --- a/crates/api/src/local_user/reset_password.rs +++ b/crates/api/src/local_user/reset_password.rs @@ -8,7 +8,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::password_reset_request::PasswordResetRequest; use lemmy_db_views::structs::{LocalUserView, SiteView}; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn reset_password( @@ -18,8 +18,8 @@ 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 - .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; + .await? + .ok_or(LemmyErrorType::IncorrectLogin)?; // Check for too many attempts (to limit potential abuse) let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count( @@ -30,7 +30,9 @@ pub async fn reset_password( if recent_resets_count >= 3 { Err(LemmyErrorType::PasswordResetLimitReached)? } - let site_view = SiteView::read_local(&mut context.pool()).await?; + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; 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 972760a00..0805eb697 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType}, + error::{LemmyErrorType, LemmyResult}, utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id}, }; @@ -34,8 +34,10 @@ pub async fn save_user_settings( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let slur_regex = local_site_to_slur_regex(&site_view.local_site); let url_blocklist = get_url_blocklist(&context).await?; diff --git a/crates/api/src/local_user/update_totp.rs b/crates/api/src/local_user/update_totp.rs index c8ca9f64e..c28ac7228 100644 --- a/crates/api/src/local_user/update_totp.rs +++ b/crates/api/src/local_user/update_totp.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; /// Enable or disable two-factor-authentication. The current setting is determined from /// [LocalUser.totp_2fa_enabled]. @@ -21,7 +21,7 @@ pub async fn update_totp( data: Json, local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { check_totp_2fa_valid( &local_user_view, &Some(data.totp_token.clone()), diff --git a/crates/api/src/local_user/validate_auth.rs b/crates/api/src/local_user/validate_auth.rs index d95195dc9..36d31ff01 100644 --- a/crates/api/src/local_user/validate_auth.rs +++ b/crates/api/src/local_user/validate_auth.rs @@ -4,7 +4,7 @@ use actix_web::{ HttpRequest, }; use lemmy_api_common::{context::LemmyContext, SuccessResponse}; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; /// Returns an error message if the auth token is invalid for any reason. Necessary because other /// endpoints silently treat any call with invalid auth as unauthenticated. @@ -12,7 +12,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType}; pub async fn validate_auth( req: HttpRequest, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { let jwt = read_auth_token(&req)?; if let Some(jwt) = jwt { local_user_view_from_jwt(&jwt, &context).await?; diff --git a/crates/api/src/local_user/verify_email.rs b/crates/api/src/local_user/verify_email.rs index 94ddb373a..da490bf63 100644 --- a/crates/api/src/local_user/verify_email.rs +++ b/crates/api/src/local_user/verify_email.rs @@ -15,17 +15,19 @@ use lemmy_db_schema::{ RegistrationMode, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; pub async fn verify_email( data: Json, context: Data, ) -> LemmyResult> { - let site_view = SiteView::read_local(&mut context.pool()).await?; + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let token = data.token.clone(); let verification = EmailVerification::read_for_token(&mut context.pool(), &token) - .await - .with_lemmy_type(LemmyErrorType::TokenNotFound)?; + .await? + .ok_or(LemmyErrorType::TokenNotFound)?; let form = LocalUserUpdateForm { // necessary in case this is a new signup @@ -44,7 +46,10 @@ pub async fn verify_email( if site_view.local_site.registration_mode == RegistrationMode::RequireApplication && site_view.local_site.application_email_admins { - let person = Person::read(&mut context.pool(), local_user.person_id).await?; + let person = Person::read(&mut context.pool(), local_user.person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; + send_new_applicant_email_to_admins(&person.name, &mut context.pool(), context.settings()) .await?; } diff --git a/crates/api/src/post/feature.rs b/crates/api/src/post/feature.rs index 8c4b4978f..40cbf6794 100644 --- a/crates/api/src/post/feature.rs +++ b/crates/api/src/post/feature.rs @@ -16,16 +16,18 @@ use lemmy_db_schema::{ PostFeatureType, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn feature_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api/src/post/get_link_metadata.rs b/crates/api/src/post/get_link_metadata.rs index a6a0c973b..17346790a 100644 --- a/crates/api/src/post/get_link_metadata.rs +++ b/crates/api/src/post/get_link_metadata.rs @@ -4,14 +4,14 @@ use lemmy_api_common::{ post::{GetSiteMetadata, GetSiteMetadataResponse}, request::fetch_link_metadata, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn get_link_metadata( data: Query, context: Data, -) -> Result, LemmyError> { - let metadata = fetch_link_metadata(&data.url, false, &context).await?; +) -> LemmyResult> { + let metadata = fetch_link_metadata(&data.url, &context).await?; Ok(Json(GetSiteMetadataResponse { metadata })) } diff --git a/crates/api/src/post/hide.rs b/crates/api/src/post/hide.rs index 1adfa110d..f7c21ef31 100644 --- a/crates/api/src/post/hide.rs +++ b/crates/api/src/post/hide.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse}; use lemmy_db_schema::source::post::PostHide; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}; use std::collections::HashSet; #[tracing::instrument(skip(context))] @@ -10,7 +10,7 @@ pub async fn hide_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_ids = HashSet::from_iter(data.post_ids.clone()); if post_ids.len() > MAX_API_PARAM_ELEMENTS { diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index d99d2a5e2..707874ded 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -21,7 +21,7 @@ use lemmy_db_schema::{ traits::{Crud, Likeable}, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use std::ops::Deref; #[tracing::instrument(skip(context))] @@ -29,7 +29,7 @@ pub async fn like_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Don't do a downvote if site has downvotes disabled @@ -38,7 +38,9 @@ pub async fn like_post( // Check for a community ban let post_id = data.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; + let post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; check_community_user_action( &local_user_view.person, @@ -69,11 +71,15 @@ pub async fn like_post( // Mark the post as read mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; + ActivityChannel::submit_activity( SendActivityData::LikePostOrComment { object_id: post.ap_id, actor: local_user_view.person.clone(), - community: Community::read(&mut context.pool(), post.community_id).await?, + community, score: data.score, }, &context, diff --git a/crates/api/src/post/list_post_likes.rs b/crates/api/src/post/list_post_likes.rs index 84690a41b..b9b2106b7 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; /// Lists likes for a post #[tracing::instrument(skip(context))] @@ -14,8 +14,10 @@ pub async fn list_post_likes( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let post = Post::read(&mut context.pool(), data.post_id).await?; +) -> LemmyResult> { + let post = Post::read(&mut context.pool(), data.post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; 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 b581f37a2..05db8ebbb 100644 --- a/crates/api/src/post/lock.rs +++ b/crates/api/src/post/lock.rs @@ -15,16 +15,18 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn lock_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; check_community_mod_action( &local_user_view.person, diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index bfc455f4f..3e534675a 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -2,7 +2,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse}; use lemmy_db_schema::source::post::PostRead; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}; use std::collections::HashSet; #[tracing::instrument(skip(context))] @@ -10,7 +10,7 @@ pub async fn mark_post_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_ids = HashSet::from_iter(data.post_ids.clone()); if post_ids.len() > MAX_API_PARAM_ELEMENTS { diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index 164840770..0876992ad 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -9,14 +9,14 @@ use lemmy_db_schema::{ traits::Saveable, }; use lemmy_db_views::structs::{LocalUserView, PostView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn save_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_saved_form = PostSavedForm { post_id: data.post_id, person_id: local_user_view.person.id, @@ -34,7 +34,9 @@ pub async fn save_post( let post_id = data.post_id; let person_id = local_user_view.person.id; - let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?; + let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; // Mark the post as read 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 1327d7bb9..72a40f70d 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ traits::Reportable, }; use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; /// Creates a post report and notifies the moderators of the community #[tracing::instrument(skip(context))] @@ -27,7 +27,7 @@ pub async fn create_post_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let reason = data.reason.trim().to_string(); @@ -35,7 +35,9 @@ 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?; + let post_view = PostView::read(&mut context.pool(), post_id, None, false) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; check_community_user_action( &local_user_view.person, @@ -59,7 +61,9 @@ 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?; + let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPostReport)?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/post_report/list.rs b/crates/api/src/post_report/list.rs index 1f1aa9653..7d1d50b0b 100644 --- a/crates/api/src/post_report/list.rs +++ b/crates/api/src/post_report/list.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ utils::check_community_mod_of_any_or_admin_action, }; use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; /// Lists post reports for a community if an id is supplied /// or returns all post reports for communities a user moderates @@ -14,7 +14,7 @@ pub async fn list_post_reports( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let community_id = data.community_id; let post_id = data.post_id; let unresolved_only = data.unresolved_only.unwrap_or_default(); diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index ab6688012..428619674 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable}; use lemmy_db_views::structs::{LocalUserView, PostReportView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; /// Resolves or unresolves a post report and notifies the moderators of the community #[tracing::instrument(skip(context))] @@ -14,10 +14,12 @@ pub async fn resolve_post_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> 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?; + let report = PostReportView::read(&mut context.pool(), report_id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPostReport)?; let person_id = local_user_view.person.id; check_community_mod_action( @@ -38,7 +40,9 @@ 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?; + let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPostReport)?; 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 6b089c0ab..07e06fe21 100644 --- a/crates/api/src/private_message/mark_read.rs +++ b/crates/api/src/private_message/mark_read.rs @@ -8,17 +8,19 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn mark_pm_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; if local_user_view.person.id != orig_private_message.recipient_id { Err(LemmyErrorType::CouldntUpdatePrivateMessage)? } @@ -37,7 +39,9 @@ pub async fn mark_pm_as_read( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; 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 7aca9661b..41ac592ae 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/private_message_report/create.rs @@ -14,14 +14,14 @@ use lemmy_db_schema::{ traits::{Crud, Reportable}, }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn create_pm_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let reason = data.reason.trim().to_string(); @@ -29,7 +29,9 @@ 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?; + let private_message = PrivateMessage::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; // Make sure that only the recipient of the private message can create a report if person_id != private_message.recipient_id { @@ -47,8 +49,9 @@ 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?; + let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?; // Email the admins if local_site.reports_email_admins { diff --git a/crates/api/src/private_message_report/list.rs b/crates/api/src/private_message_report/list.rs index 2dc3e6efc..79ef53e1c 100644 --- a/crates/api/src/private_message_report/list.rs +++ b/crates/api/src/private_message_report/list.rs @@ -8,14 +8,14 @@ use lemmy_db_views::{ private_message_report_view::PrivateMessageReportQuery, structs::LocalUserView, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_pm_reports( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { is_admin(&local_user_view)?; let unresolved_only = data.unresolved_only.unwrap_or_default(); diff --git a/crates/api/src/private_message_report/resolve.rs b/crates/api/src/private_message_report/resolve.rs index 202fdcd29..27847eeaf 100644 --- a/crates/api/src/private_message_report/resolve.rs +++ b/crates/api/src/private_message_report/resolve.rs @@ -6,14 +6,14 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable}; use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn resolve_pm_report( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { is_admin(&local_user_view)?; let report_id = data.report_id; @@ -28,8 +28,9 @@ pub async fn resolve_pm_report( .with_lemmy_type(LemmyErrorType::CouldntResolveReport)?; } - let private_message_report_view = - PrivateMessageReportView::read(&mut context.pool(), report_id).await?; + let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?; Ok(Json(PrivateMessageReportResponse { private_message_report_view, diff --git a/crates/api/src/site/block.rs b/crates/api/src/site/block.rs index 7d28e43d5..823dda612 100644 --- a/crates/api/src/site/block.rs +++ b/crates/api/src/site/block.rs @@ -9,14 +9,14 @@ use lemmy_db_schema::{ traits::Blockable, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn block_instance( data: Json, local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { let instance_id = data.instance_id; let person_id = local_user_view.person.id; if local_user_view.person.instance_id == instance_id { diff --git a/crates/api/src/site/federated_instances.rs b/crates/api/src/site/federated_instances.rs index 8f224b2eb..66b0ff2c1 100644 --- a/crates/api/src/site/federated_instances.rs +++ b/crates/api/src/site/federated_instances.rs @@ -5,13 +5,15 @@ use lemmy_api_common::{ utils::build_federated_instances, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn get_federated_instances( context: Data, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; 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 f2db0fc26..e7a5464f3 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -14,7 +14,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType}, + error::{LemmyErrorType, LemmyResult}, VERSION, }; @@ -22,7 +22,7 @@ use lemmy_utils::{ pub async fn leave_admin( context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { is_admin(&local_user_view)?; // Make sure there isn't just one admin (so if one leaves, there will still be one left) @@ -55,7 +55,9 @@ 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?; + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let admins = PersonView::admins(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?; diff --git a/crates/api/src/site/list_all_media.rs b/crates/api/src/site/list_all_media.rs index 495e72e48..4d8d2dc2a 100644 --- a/crates/api/src/site/list_all_media.rs +++ b/crates/api/src/site/list_all_media.rs @@ -4,21 +4,20 @@ use lemmy_api_common::{ person::{ListMedia, ListMediaResponse}, utils::is_admin, }; -use lemmy_db_schema::source::images::LocalImage; -use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_db_views::structs::{LocalImageView, LocalUserView}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn list_all_media( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Only let admins view all media is_admin(&local_user_view)?; let page = data.page; let limit = data.limit; - let images = LocalImage::get_all(&mut context.pool(), page, limit).await?; + let images = LocalImageView::get_all(&mut context.pool(), page, limit).await?; Ok(Json(ListMediaResponse { images })) } diff --git a/crates/api/src/site/mod_log.rs b/crates/api/src/site/mod_log.rs index 1a4148d89..8f5538566 100644 --- a/crates/api/src/site/mod_log.rs +++ b/crates/api/src/site/mod_log.rs @@ -24,7 +24,7 @@ use lemmy_db_views_moderator::structs::{ ModTransferCommunityView, ModlogListParams, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use ModlogActionType::*; #[tracing::instrument(skip(context))] @@ -32,7 +32,7 @@ pub async fn get_mod_log( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site)?; diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index a06085f24..70d95e160 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -15,21 +15,23 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn purge_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Only let admin purge an item is_admin(&local_user_view)?; let comment_id = data.comment_id; // Read the comment to get the post_id and community - let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?; + let comment_view = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; let post_id = comment_view.comment.post_id; diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index 61e58ba04..14b250681 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -16,19 +16,21 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn purge_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Only let admin purge an item is_admin(&local_user_view)?; // Read the community to get its images - let community = Community::read(&mut context.pool(), data.community_id).await?; + let community = Community::read(&mut context.pool(), data.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if let Some(banner) = &community.banner { purge_image_from_pictrs(banner, &context).await.ok(); diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index 6023d7b41..1b38752c7 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -16,18 +16,20 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn purge_person( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Only let admin purge an item is_admin(&local_user_view)?; - let person = Person::read(&mut context.pool(), data.person_id).await?; + let person = Person::read(&mut context.pool(), data.person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; ban_nonlocal_user_from_local_communities( &local_user_view, &person, diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index 28e6668ff..75cd021d1 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -16,19 +16,21 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn purge_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Only let admin purge an item is_admin(&local_user_view)?; // Read the post to get the community_id - let post = Post::read(&mut context.pool(), data.post_id).await?; + let post = Post::read(&mut context.pool(), data.post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; // Purge image if let Some(url) = &post.url { diff --git a/crates/api/src/site/registration_applications/approve.rs b/crates/api/src/site/registration_applications/approve.rs index 036a60e00..0fb55ffc8 100644 --- a/crates/api/src/site/registration_applications/approve.rs +++ b/crates/api/src/site/registration_applications/approve.rs @@ -13,13 +13,13 @@ use lemmy_db_schema::{ utils::diesel_option_overwrite, }; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; pub async fn approve_registration_application( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let app_id = data.id; // Only let admins do this @@ -45,8 +45,9 @@ pub async fn approve_registration_application( LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?; if data.approve { - let approved_local_user_view = - LocalUserView::read(&mut context.pool(), approved_user_id).await?; + let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; if approved_local_user_view.local_user.email.is_some() { send_application_approved_email(&approved_local_user_view, context.settings()).await?; @@ -54,8 +55,9 @@ pub async fn approve_registration_application( } // Read the view - let registration_application = - RegistrationApplicationView::read(&mut context.pool(), app_id).await?; + let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id) + .await? + .ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?; Ok(Json(RegistrationApplicationResponse { registration_application, diff --git a/crates/api/src/site/registration_applications/list.rs b/crates/api/src/site/registration_applications/list.rs index 30ce9aaf2..df86b11d5 100644 --- a/crates/api/src/site/registration_applications/list.rs +++ b/crates/api/src/site/registration_applications/list.rs @@ -9,14 +9,14 @@ use lemmy_db_views::{ registration_application_view::RegistrationApplicationQuery, structs::LocalUserView, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; /// Lists registration applications, filterable by undenied only. pub async fn list_registration_applications( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin diff --git a/crates/api/src/site/registration_applications/unread_count.rs b/crates/api/src/site/registration_applications/unread_count.rs index 255859198..a12ecb1d3 100644 --- a/crates/api/src/site/registration_applications/unread_count.rs +++ b/crates/api/src/site/registration_applications/unread_count.rs @@ -6,12 +6,12 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub async fn get_unread_registration_application_count( context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Only let admins do this diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 47545446f..9b196c426 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -72,7 +72,7 @@ webpage = { version = "1.6", default-features = false, features = [ encoding = { version = "0.2.33", optional = true } jsonwebtoken = { version = "8.3.0", optional = true } # necessary for wasmt compilation -getrandom = { version = "0.2.12", features = ["js"] } +getrandom = { version = "0.2.14", features = ["js"] } [package.metadata.cargo-machete] ignored = ["getrandom"] diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index 19e0bb46b..f77a90882 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -19,15 +19,15 @@ use lemmy_db_schema::{ comment_reply::{CommentReply, CommentReplyInsertForm}, person::Person, person_mention::{PersonMention, PersonMentionInsertForm}, - post::Post, }, traits::Crud, }; use lemmy_db_views::structs::{CommentView, LocalUserView, PostView}; use lemmy_db_views_actor::structs::CommunityView; use lemmy_utils::{ - error::LemmyError, + error::LemmyResult, utils::{markdown::markdown_to_html, mention::MentionData}, + LemmyErrorType, }; pub async fn build_comment_response( @@ -35,9 +35,11 @@ pub async fn build_comment_response( comment_id: CommentId, local_user_view: Option, recipient_ids: Vec, -) -> Result { +) -> LemmyResult { let person_id = local_user_view.map(|l| l.person.id); - let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id).await?; + let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; Ok(CommentResponse { comment_view, recipient_ids, @@ -48,7 +50,7 @@ pub async fn build_community_response( context: &LemmyContext, local_user_view: LocalUserView, community_id: CommunityId, -) -> Result, LemmyError> { +) -> LemmyResult> { let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id) .await .is_ok(); @@ -59,7 +61,8 @@ pub async fn build_community_response( Some(person_id), is_mod_or_admin, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; Ok(Json(CommunityResponse { @@ -73,7 +76,7 @@ pub async fn build_post_response( community_id: CommunityId, person: &Person, post_id: PostId, -) -> Result, LemmyError> { +) -> LemmyResult> { let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id) .await .is_ok(); @@ -83,7 +86,8 @@ pub async fn build_post_response( Some(person.id), is_mod_or_admin, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; Ok(Json(PostResponse { post_view })) } @@ -91,16 +95,21 @@ pub async fn build_post_response( #[tracing::instrument(skip_all)] pub async fn send_local_notifs( mentions: Vec, - comment: &Comment, + comment_id: CommentId, person: &Person, - post: &Post, do_send_email: bool, context: &LemmyContext, -) -> Result, LemmyError> { +) -> LemmyResult> { let mut recipient_ids = Vec::new(); let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname()); - let community_id = post.community_id; + // Read the comment view to get extra info + let comment_view = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; + let comment = comment_view.comment; + let post = comment_view.post; + let community = comment_view.community; // Send the local mentions for mention in mentions @@ -109,7 +118,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(mention_user_view) = user_view { + if let Ok(Some(mention_user_view)) = user_view { // TODO // At some point, make it so you can't tag the parent creator either // Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids @@ -117,7 +126,7 @@ pub async fn send_local_notifs( let user_mention_form = PersonMentionInsertForm { recipient_id: mention_user_view.person.id, - comment_id: comment.id, + comment_id, read: None, }; @@ -144,7 +153,9 @@ 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?; + let parent_comment = Comment::read(&mut context.pool(), parent_comment_id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; // Get the parent commenter local_user let parent_creator_id = parent_comment.creator_id; @@ -152,8 +163,9 @@ pub async fn send_local_notifs( let check_blocks = check_person_instance_community_block( person.id, parent_creator_id, - person.instance_id, - community_id, + // Only block from the community's instance_id + community.instance_id, + community.id, &mut context.pool(), ) .await @@ -162,7 +174,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(parent_user_view) = user_view { + if let Ok(Some(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); @@ -194,11 +206,13 @@ pub async fn send_local_notifs( } } } else { + // Use the post creator to check blocks let check_blocks = check_person_instance_community_block( person.id, post.creator_id, - person.instance_id, - community_id, + // Only block from the community's instance_id + community.instance_id, + community.id, &mut context.pool(), ) .await @@ -207,7 +221,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(parent_user_view) = parent_user { + if let Ok(Some(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 19145488a..a926cb0ba 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -99,7 +99,7 @@ mod tests { async fn test_should_not_validate_user_token_after_password_change() { let pool_ = build_db_pool_for_tests().await; let pool = &mut (&pool_).into(); - let secret = Secret::init(pool).await.unwrap(); + let secret = Secret::init(pool).await.unwrap().unwrap(); let context = LemmyContext::create( pool_.clone(), ClientBuilder::new(Client::default()).build(), diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index b55dff32f..4bb7ada5b 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -27,7 +27,7 @@ pub extern crate lemmy_utils; pub use lemmy_utils::LemmyErrorType; use serde::{Deserialize, Serialize}; -use std::time::Duration; +use std::{cmp::min, time::Duration}; #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(ts_rs::TS))] @@ -43,7 +43,39 @@ impl Default for SuccessResponse { } } -/// how long to sleep based on how many retries have already happened +// TODO: use from_days once stabilized +// https://github.com/rust-lang/rust/issues/120301 +const DAY: Duration = Duration::from_secs(24 * 60 * 60); + +/// Calculate how long to sleep until next federation send based on how many +/// retries have already happened. Uses exponential backoff with maximum of one day. The first +/// error is ignored. pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration { - Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count))) + debug_assert!(retry_count != 0); + if retry_count == 1 { + return Duration::from_secs(0); + } + let retry_count = retry_count - 1; + let pow = 1.25_f64.powf(retry_count.into()); + let pow = Duration::try_from_secs_f64(pow).unwrap_or(DAY); + min(DAY, pow) +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + #[test] + fn test_federate_retry_sleep_duration() { + assert_eq!(Duration::from_secs(0), federate_retry_sleep_duration(1)); + assert_eq!( + Duration::new(1, 250000000), + federate_retry_sleep_duration(2) + ); + assert_eq!( + Duration::new(2, 441406250), + federate_retry_sleep_duration(5) + ); + assert_eq!(DAY, federate_retry_sleep_duration(100)); + } } diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index a4f9b64d9..4f5aea2be 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -1,13 +1,13 @@ use crate::sensitive::Sensitive; use lemmy_db_schema::{ newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId}, - source::{images::LocalImage, site::Site}, + source::site::Site, CommentSortType, ListingType, PostListingMode, SortType, }; -use lemmy_db_views::structs::{CommentView, PostView}; +use lemmy_db_views::structs::{CommentView, LocalImageView, PostView}; use lemmy_db_views_actor::structs::{ CommentReplyView, CommunityModeratorView, @@ -437,5 +437,5 @@ pub struct ListMedia { #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] pub struct ListMediaResponse { - pub images: Vec, + pub images: Vec, } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 4993fc6e5..49327dac1 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -270,8 +270,6 @@ pub struct LinkMetadata { #[serde(flatten)] pub opengraph_data: OpenGraphData, pub content_type: Option, - #[serde(skip)] - pub thumbnail: Option, } #[skip_serializing_none] diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index d77ea2daa..ddb2a4551 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ }, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType}, + error::{LemmyError, LemmyErrorType, LemmyResult}, settings::structs::{PictrsImageMode, Settings}, spawn_try_task, REQWEST_TIMEOUT, @@ -42,11 +42,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder { /// Fetches metadata for the given link and optionally generates thumbnail. #[tracing::instrument(skip_all)] -pub async fn fetch_link_metadata( - url: &Url, - generate_thumbnail: bool, - context: &LemmyContext, -) -> Result { +pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResult { info!("Fetching site metadata for url: {}", url); let response = context.client().get(url.as_str()).send().await?; @@ -63,63 +59,70 @@ pub async fn fetch_link_metadata( let opengraph_data = extract_opengraph_data(&html_bytes, url) .map_err(|e| info!("{e}")) .unwrap_or_default(); - let thumbnail = - extract_thumbnail_from_opengraph_data(url, &opengraph_data, generate_thumbnail, context).await; - Ok(LinkMetadata { opengraph_data, content_type: content_type.map(|c| c.to_string()), - thumbnail, }) } -#[tracing::instrument(skip_all)] -pub async fn fetch_link_metadata_opt( - url: Option<&Url>, - generate_thumbnail: bool, - context: &LemmyContext, -) -> LinkMetadata { - match &url { - Some(url) => fetch_link_metadata(url, generate_thumbnail, context) - .await - .unwrap_or_default(), - _ => Default::default(), - } -} /// Generate post thumbnail in background task, because some sites can be very slow to respond. /// /// Takes a callback to generate a send activity task, so that post can be federated with metadata. +/// +/// TODO: `federated_thumbnail` param can be removed once we federate full metadata and can +/// write it to db directly, without calling this function. +/// https://github.com/LemmyNet/lemmy/issues/4598 pub fn generate_post_link_metadata( post: Post, custom_thumbnail: Option, + federated_thumbnail: Option, send_activity: impl FnOnce(Post) -> Option + Send + 'static, local_site: Option, context: Data, ) { spawn_try_task(async move { + let metadata = match &post.url { + Some(url) => fetch_link_metadata(url, &context).await.unwrap_or_default(), + _ => Default::default(), + }; + + let is_image_post = metadata + .content_type + .as_ref() + .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 page_is_sensitive = post.nsfw; - let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive; - let mut thumbnail_url = custom_thumbnail.or_else(|| post.thumbnail_url.map(Into::into)); - let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail; - - // Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it. - let metadata = fetch_link_metadata_opt( - post.url.map(Into::into).as_ref(), - do_generate_thumbnail, - &context, - ) - .await; - if let Some(thumbnail_url_) = metadata.thumbnail { - thumbnail_url = Some(thumbnail_url_.into()); + let allow_generate_thumbnail = allow_sensitive || !post.nsfw; + + // Use custom thumbnail if available and its not an image post + let thumbnail_url = if !is_image_post && custom_thumbnail.is_some() { + custom_thumbnail + } + // Use federated thumbnail if available + else if federated_thumbnail.is_some() { + federated_thumbnail + } + // Generate local thumbnail if allowed + else if allow_generate_thumbnail { + match post.url.or(metadata.opengraph_data.image) { + Some(url) => generate_pictrs_thumbnail(&url, &context).await.ok(), + None => None, + } } - let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, &context).await?; + // Otherwise use opengraph preview image directly + else { + metadata.opengraph_data.image.map(Into::into) + }; + + // Proxy the image fetch if necessary + let proxied_thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, &context).await?; let form = PostUpdateForm { embed_title: Some(metadata.opengraph_data.title), embed_description: Some(metadata.opengraph_data.description), embed_video_url: Some(metadata.opengraph_data.embed_video_url), - thumbnail_url: Some(thumbnail_url), + thumbnail_url: Some(proxied_thumbnail_url), url_content_type: Some(metadata.content_type), ..Default::default() }; @@ -132,7 +135,7 @@ pub fn generate_post_link_metadata( } /// Extract site metadata from HTML Opengraph attributes. -fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result { +fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> LemmyResult { let html = String::from_utf8_lossy(html_bytes); // Make sure the first line is doctype html @@ -196,28 +199,6 @@ fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result Option { - if generate_thumbnail { - let image_url = opengraph_data - .image - .as_ref() - .map(DbUrl::inner) - .unwrap_or(url); - generate_pictrs_thumbnail(image_url, context) - .await - .ok() - .map(Into::into) - } else { - opengraph_data.image.clone() - } -} - #[derive(Deserialize, Debug)] struct PictrsResponse { files: Vec, @@ -240,10 +221,7 @@ struct PictrsPurgeResponse { /// - It might fail due to image being not local /// - It might not be an image /// - Pictrs might not be set up -pub async fn purge_image_from_pictrs( - image_url: &Url, - context: &LemmyContext, -) -> Result<(), LemmyError> { +pub async fn purge_image_from_pictrs(image_url: &Url, context: &LemmyContext) -> LemmyResult<()> { is_image_content_type(context.client(), image_url).await?; let alias = image_url @@ -278,7 +256,7 @@ pub async fn delete_image_from_pictrs( alias: &str, delete_token: &str, context: &LemmyContext, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let pictrs_config = context.settings().pictrs_config()?; let url = format!( "{}image/delete/{}/{}", @@ -296,15 +274,16 @@ pub async fn delete_image_from_pictrs( /// Retrieves the image with local pict-rs and generates a thumbnail. Returns the thumbnail url. #[tracing::instrument(skip_all)] -async fn generate_pictrs_thumbnail( - image_url: &Url, - context: &LemmyContext, -) -> Result { +async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult { let pictrs_config = context.settings().pictrs_config()?; - if pictrs_config.image_mode() == PictrsImageMode::ProxyAllImages { - return Ok(proxy_image_link(image_url.clone(), context).await?.into()); - } + match pictrs_config.image_mode() { + PictrsImageMode::None => return Ok(image_url.clone()), + PictrsImageMode::ProxyAllImages => { + return Ok(proxy_image_link(image_url.clone(), context).await?.into()) + } + _ => {} + }; // fetch remote non-pictrs images for persistent thumbnail link // TODO: should limit size once supported by pictrs @@ -345,7 +324,7 @@ async fn generate_pictrs_thumbnail( // TODO: get rid of this by reading content type from db #[tracing::instrument(skip_all)] -async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Result<(), LemmyError> { +async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> LemmyResult<()> { let response = client.get(url.as_str()).send().await?; if response .headers() @@ -365,7 +344,7 @@ pub async fn replace_image( new_image: &Option, old_image: &Option, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { if new_image.is_some() { // Ignore errors because image may be stored externally. if let Some(avatar) = &old_image { @@ -399,9 +378,7 @@ mod tests { async fn test_link_metadata() { 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, false, &context) - .await - .unwrap(); + let sample_res = fetch_link_metadata(&sample_url, &context).await.unwrap(); assert_eq!( Some("FAQ · Wiki · IzzyOnDroid / repo · GitLab".to_string()), sample_res.opengraph_data.title @@ -423,17 +400,8 @@ mod tests { Some(mime::TEXT_HTML_UTF_8.to_string()), sample_res.content_type ); - assert!(sample_res.thumbnail.is_some()); } - // #[test] - // fn test_pictshare() { - // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg"); - // assert!(res.is_ok()); - // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu"); - // assert!(res_other.is_err()); - // } - #[test] fn test_resolve_image_url() { // url that lists the opengraph fields diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index ad416ffbb..3cd7902e1 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -12,7 +12,7 @@ use lemmy_db_schema::{ community::{Community, CommunityModerator, CommunityUpdateForm}, community_block::CommunityBlock, email_verification::{EmailVerification, EmailVerificationForm}, - images::{LocalImage, RemoteImage}, + images::RemoteImage, instance::Instance, instance_block::InstanceBlock, local_site::LocalSite, @@ -27,7 +27,10 @@ use lemmy_db_schema::{ traits::Crud, utils::DbPool, }; -use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView}; +use lemmy_db_views::{ + comment_view::CommentQuery, + structs::{LocalImageView, LocalUserView}, +}; use lemmy_db_views_actor::structs::{ CommunityModeratorView, CommunityPersonBanView, @@ -42,7 +45,7 @@ use lemmy_utils::{ markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links}, slurs::{build_slur_regex, remove_slurs}, }, - CACHE_DURATION_SHORT, + CACHE_DURATION_FEDERATION, }; use moka::future::Cache; use once_cell::sync::Lazy; @@ -60,7 +63,7 @@ pub async fn is_mod_or_admin( pool: &mut DbPool<'_>, person: &Person, community_id: CommunityId, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { check_user_valid(person)?; let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?; @@ -76,7 +79,7 @@ pub async fn is_mod_or_admin_opt( pool: &mut DbPool<'_>, local_user_view: Option<&LocalUserView>, community_id: Option, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { if let Some(local_user_view) = local_user_view { if let Some(community_id) = community_id { is_mod_or_admin(pool, &local_user_view.person, community_id).await @@ -108,7 +111,7 @@ pub async fn check_community_mod_of_any_or_admin_action( } } -pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> { +pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> { check_user_valid(&local_user_view.person)?; if !local_user_view.local_user.admin { Err(LemmyErrorType::NotAnAdmin)? @@ -122,7 +125,7 @@ pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> { pub fn is_top_mod( local_user_view: &LocalUserView, community_mods: &[CommunityModeratorView], -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { check_user_valid(&local_user_view.person)?; if local_user_view.person.id != community_mods @@ -137,10 +140,10 @@ pub fn is_top_mod( } #[tracing::instrument(skip_all)] -pub async fn get_post(post_id: PostId, pool: &mut DbPool<'_>) -> Result { +pub async fn get_post(post_id: PostId, pool: &mut DbPool<'_>) -> LemmyResult { Post::read(pool, post_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPost) + .await? + .ok_or(LemmyErrorType::CouldntFindPost.into()) } #[tracing::instrument(skip_all)] @@ -148,14 +151,14 @@ pub async fn mark_post_as_read( person_id: PersonId, post_id: PostId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { PostRead::mark_as_read(pool, HashSet::from([post_id]), person_id) .await .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; Ok(()) } -pub fn check_user_valid(person: &Person) -> Result<(), LemmyError> { +pub fn check_user_valid(person: &Person) -> LemmyResult<()> { // Check for a site ban if person.banned { Err(LemmyErrorType::SiteBan)? @@ -188,8 +191,8 @@ async fn check_community_deleted_removed( pool: &mut DbPool<'_>, ) -> LemmyResult<()> { let community = Community::read(pool, community_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if community.deleted || community.removed { Err(LemmyErrorType::Deleted)? } @@ -230,7 +233,7 @@ pub async fn check_community_mod_action( } /// Don't allow creating reports for removed / deleted posts -pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> { +pub fn check_post_deleted_or_removed(post: &Post) -> LemmyResult<()> { if post.deleted || post.removed { Err(LemmyErrorType::Deleted)? } else { @@ -238,7 +241,7 @@ pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> { } } -pub fn check_comment_deleted_or_removed(comment: &Comment) -> Result<(), LemmyError> { +pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> { if comment.deleted || comment.removed { Err(LemmyErrorType::Deleted)? } else { @@ -252,7 +255,7 @@ pub async fn check_person_block( my_id: PersonId, potential_blocker_id: PersonId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?; if is_blocked { Err(LemmyErrorType::PersonIsBlocked)? @@ -267,7 +270,7 @@ async fn check_community_block( community_id: CommunityId, person_id: PersonId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?; if is_blocked { Err(LemmyErrorType::CommunityIsBlocked)? @@ -282,7 +285,7 @@ async fn check_instance_block( instance_id: InstanceId, person_id: PersonId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?; if is_blocked { Err(LemmyErrorType::InstanceIsBlocked)? @@ -295,18 +298,18 @@ async fn check_instance_block( pub async fn check_person_instance_community_block( my_id: PersonId, potential_blocker_id: PersonId, - instance_id: InstanceId, + community_instance_id: InstanceId, community_id: CommunityId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { check_person_block(my_id, potential_blocker_id, pool).await?; - check_instance_block(instance_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?; Ok(()) } #[tracing::instrument(skip_all)] -pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> { +pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> { if score == -1 && !local_site.enable_downvotes { Err(LemmyErrorType::DownvotesAreDisabled)? } else { @@ -316,7 +319,7 @@ pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), /// Dont allow bots to do certain actions, like voting #[tracing::instrument(skip_all)] -pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> { +pub fn check_bot_account(person: &Person) -> LemmyResult<()> { if person.bot_account { Err(LemmyErrorType::InvalidBotAction)? } else { @@ -328,7 +331,7 @@ pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> { pub fn check_private_instance( local_user_view: &Option, local_site: &LocalSite, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { if local_user_view.is_none() && local_site.private_instance { Err(LemmyErrorType::InstanceIsPrivate)? } else { @@ -340,7 +343,7 @@ pub fn check_private_instance( pub async fn build_federated_instances( local_site: &LocalSite, pool: &mut DbPool<'_>, -) -> Result, LemmyError> { +) -> LemmyResult> { if local_site.federation_enabled { let mut linked = Vec::new(); let mut allowed = Vec::new(); @@ -375,7 +378,7 @@ pub async fn build_federated_instances( } /// Checks the password length -pub fn password_length_check(pass: &str) -> Result<(), LemmyError> { +pub fn password_length_check(pass: &str) -> LemmyResult<()> { if !(10..=60).contains(&pass.chars().count()) { Err(LemmyErrorType::InvalidPassword)? } else { @@ -384,7 +387,7 @@ pub fn password_length_check(pass: &str) -> Result<(), LemmyError> { } /// Checks for a honeypot. If this field is filled, fail the rest of the function -pub fn honeypot_check(honeypot: &Option) -> Result<(), LemmyError> { +pub fn honeypot_check(honeypot: &Option) -> LemmyResult<()> { if honeypot.is_some() && honeypot != &Some(String::new()) { Err(LemmyErrorType::HoneypotFailed)? } else { @@ -422,7 +425,7 @@ pub async fn send_password_reset_email( user: &LocalUserView, pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { // Generate a random token let token = uuid::Uuid::new_v4().to_string(); @@ -447,7 +450,7 @@ pub async fn send_verification_email( new_email: &str, pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let form = EmailVerificationForm { local_user_id: user.local_user.id, email: new_email.to_string(), @@ -524,7 +527,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult static URL_BLOCKLIST: Lazy> = Lazy::new(|| { Cache::builder() .max_capacity(1) - .time_to_live(CACHE_DURATION_SHORT) + .time_to_live(CACHE_DURATION_FEDERATION) .build() }); @@ -564,7 +567,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult pub async fn send_application_approved_email( user: &LocalUserView, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let email = &user.local_user.email.clone().expect("email"); let lang = get_interface_language(user); let subject = lang.registration_approved_subject(&user.person.actor_id); @@ -577,7 +580,7 @@ pub async fn send_new_applicant_email_to_admins( applicant_username: &str, pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { // Collect the admins with emails let admins = LocalUserView::list_admins_with_emails(pool).await?; @@ -602,7 +605,7 @@ pub async fn send_new_report_email_to_admins( reported_username: &str, pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { // Collect the admins with emails let admins = LocalUserView::list_admins_with_emails(pool).await?; @@ -618,9 +621,7 @@ pub async fn send_new_report_email_to_admins( Ok(()) } -pub fn check_private_instance_and_federation_enabled( - local_site: &LocalSite, -) -> Result<(), LemmyError> { +pub fn check_private_instance_and_federation_enabled(local_site: &LocalSite) -> LemmyResult<()> { if local_site.private_instance && local_site.federation_enabled { Err(LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether)? } else { @@ -634,7 +635,7 @@ pub fn check_private_instance_and_federation_enabled( pub async fn read_site_for_actor( actor_id: DbUrl, context: &LemmyContext, -) -> Result, LemmyError> { +) -> LemmyResult> { let site_id = Site::instance_actor_id_from_url(actor_id.clone().into()); let site = Site::read_from_apub_id(&mut context.pool(), &site_id.into()).await?; Ok(site) @@ -643,7 +644,7 @@ pub async fn read_site_for_actor( pub async fn purge_image_posts_for_person( banned_person_id: PersonId, context: &LemmyContext, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let pool = &mut context.pool(); let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?; for post in posts { @@ -661,19 +662,21 @@ 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, -) -> Result<(), LemmyError> { - if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await { +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 { let pictrs_uploads = - LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?; + LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id) + .await?; // Delete their images for upload in pictrs_uploads { - delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, context) - .await - .ok(); + delete_image_from_pictrs( + &upload.local_image.pictrs_alias, + &upload.local_image.pictrs_delete_token, + context, + ) + .await + .ok(); } } Ok(()) @@ -682,7 +685,7 @@ async fn delete_local_user_images( pub async fn purge_image_posts_for_community( banned_community_id: CommunityId, context: &LemmyContext, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let pool = &mut context.pool(); let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?; for post in posts { @@ -702,10 +705,12 @@ pub async fn purge_image_posts_for_community( pub async fn remove_user_data( banned_person_id: PersonId, context: &LemmyContext, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let pool = &mut context.pool(); // Purge user images - let person = Person::read(pool, banned_person_id).await?; + 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(); } @@ -785,7 +790,7 @@ pub async fn remove_user_data_in_community( community_id: CommunityId, banned_person_id: PersonId, pool: &mut DbPool<'_>, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { // Posts Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?; @@ -815,13 +820,12 @@ pub async fn remove_user_data_in_community( Ok(()) } -pub async fn purge_user_account( - person_id: PersonId, - context: &LemmyContext, -) -> Result<(), LemmyError> { +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?; + let person = Person::read(pool, person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; // Delete their local images, if they're a local user delete_local_user_images(person_id, context).await.ok(); @@ -888,7 +892,7 @@ 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) -> Result { +pub fn generate_shared_inbox_url(settings: &Settings) -> LemmyResult { let url = format!("{}/inbox", settings.get_protocol_and_hostname()); Ok(Url::parse(&url)?.into()) } @@ -901,7 +905,7 @@ pub fn generate_featured_url(actor_id: &DbUrl) -> Result { Ok(Url::parse(&format!("{actor_id}/featured"))?.into()) } -pub fn generate_moderators_url(community_id: &DbUrl) -> Result { +pub fn generate_moderators_url(community_id: &DbUrl) -> LemmyResult { Ok(Url::parse(&format!("{community_id}/moderators"))?.into()) } diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index acb386c60..6493d6803 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -30,7 +30,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field}, }; @@ -41,7 +41,7 @@ pub async fn create_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let slur_regex = local_site_to_slur_regex(&local_site); @@ -70,7 +70,8 @@ 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 @@ -138,9 +139,8 @@ pub async fn create_comment( let mentions = scrape_text_for_mentions(&content); let recipient_ids = send_local_notifs( mentions, - &updated_comment, + inserted_comment_id, &local_user_view.person, - &post, true, &context, ) @@ -173,7 +173,7 @@ pub async fn create_comment( let parent_id = parent.id; let comment_reply = CommentReply::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await; - if let Ok(reply) = comment_reply { + if let Ok(Some(reply)) = comment_reply { CommentReply::update( &mut context.pool(), reply.id, @@ -186,7 +186,7 @@ pub async fn create_comment( // If the parent has PersonMentions mark them as read too let person_mention = PersonMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await; - if let Ok(mention) = person_mention { + if let Ok(Some(mention)) = person_mention { PersonMention::update( &mut context.pool(), mention.id, @@ -208,7 +208,7 @@ pub async fn create_comment( )) } -pub fn check_comment_depth(comment: &Comment) -> Result<(), LemmyError> { +pub fn check_comment_depth(comment: &Comment) -> LemmyResult<()> { let path = &comment.path.0; let length = path.split('.').count(); if length > MAX_COMMENT_DEPTH_LIMIT { diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 2de2a7955..2b1a20f89 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -8,23 +8,22 @@ use lemmy_api_common::{ utils::check_community_user_action, }; use lemmy_db_schema::{ - source::{ - comment::{Comment, CommentUpdateForm}, - post::Post, - }, + source::comment::{Comment, CommentUpdateForm}, traits::Crud, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn delete_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; // Dont delete it if its already been deleted. if orig_comment.comment.deleted == data.deleted { @@ -56,17 +55,8 @@ pub async fn delete_comment( .await .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; - let post_id = updated_comment.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; - let recipient_ids = send_local_notifs( - vec![], - &updated_comment, - &local_user_view.person, - &post, - false, - &context, - ) - .await?; + let recipient_ids = + send_local_notifs(vec![], comment_id, &local_user_view.person, false, &context).await?; let updated_comment_id = updated_comment.id; ActivityChannel::submit_activity( diff --git a/crates/api_crud/src/comment/read.rs b/crates/api_crud/src/comment/read.rs index 733d08682..39852081f 100644 --- a/crates/api_crud/src/comment/read.rs +++ b/crates/api_crud/src/comment/read.rs @@ -7,14 +7,14 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn get_comment( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site)?; diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index 5bb6f55b1..02ae7b9fd 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -12,21 +12,22 @@ use lemmy_db_schema::{ comment::{Comment, CommentUpdateForm}, comment_report::CommentReport, moderator::{ModRemoveComment, ModRemoveCommentForm}, - post::Post, }, traits::{Crud, Reportable}, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn remove_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; check_community_mod_action( &local_user_view.person, @@ -61,13 +62,10 @@ pub async fn remove_comment( }; ModRemoveComment::create(&mut context.pool(), &form).await?; - let post_id = updated_comment.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; let recipient_ids = send_local_notifs( vec![], - &updated_comment, + comment_id, &local_user_view.person.clone(), - &post, false, &context, ) diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index e814ebd6b..695ededfe 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -23,7 +23,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{CommentView, LocalUserView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field}, }; @@ -32,11 +32,13 @@ pub async fn update_comment( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let comment_id = data.comment_id; - let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; + let orig_comment = CommentView::read(&mut context.pool(), comment_id, None) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; check_community_user_action( &local_user_view.person, @@ -79,9 +81,8 @@ pub async fn update_comment( let mentions = scrape_text_for_mentions(&updated_comment_content); let recipient_ids = send_local_notifs( mentions, - &updated_comment, + comment_id, &local_user_view.person, - &orig_comment.post, false, &context, ) diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 679655078..b0b6bea0e 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -33,7 +33,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::check_slurs, validation::{is_valid_actor_name, is_valid_body_field}, @@ -45,8 +45,10 @@ pub async fn create_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let local_site = site_view.local_site; if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() { diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index 60b79fd79..a2ceaff50 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -13,14 +13,14 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn delete_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Fetch the community mods let community_id = data.community_id; let community_mods = diff --git a/crates/api_crud/src/community/list.rs b/crates/api_crud/src/community/list.rs index 7990352fc..587b5cdfa 100644 --- a/crates/api_crud/src/community/list.rs +++ b/crates/api_crud/src/community/list.rs @@ -6,15 +6,17 @@ use lemmy_api_common::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::community_view::CommunityQuery; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn list_communities( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { - let local_site = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let local_site = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let is_admin = local_user_view .as_ref() .map(|luv| is_admin(luv).is_ok()) diff --git a/crates/api_crud/src/community/remove.rs b/crates/api_crud/src/community/remove.rs index d7be60927..f4271565d 100644 --- a/crates/api_crud/src/community/remove.rs +++ b/crates/api_crud/src/community/remove.rs @@ -15,14 +15,14 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn remove_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { check_community_mod_action( &local_user_view.person, data.community_id, diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 51c57e1c8..33c6a47dd 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{slurs::check_slurs_opt, validation::is_valid_body_field}, }; @@ -34,7 +34,7 @@ pub async fn update_community( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let slur_regex = local_site_to_slur_regex(&local_site); @@ -43,7 +43,9 @@ pub async fn update_community( let description = process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?; is_valid_body_field(&data.description, false)?; - let old_community = Community::read(&mut context.pool(), data.community_id).await?; + let old_community = Community::read(&mut context.pool(), data.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; replace_image(&data.icon, &old_community.icon, &context).await?; replace_image(&data.banner, &old_community.banner, &context).await?; diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs index cd30ef1e9..3c5ce3296 100644 --- a/crates/api_crud/src/custom_emoji/create.rs +++ b/crates/api_crud/src/custom_emoji/create.rs @@ -11,14 +11,14 @@ use lemmy_db_schema::source::{ local_site::LocalSite, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn create_custom_emoji( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/custom_emoji/delete.rs b/crates/api_crud/src/custom_emoji/delete.rs index 93c5f8d80..45ac8d0ba 100644 --- a/crates/api_crud/src/custom_emoji/delete.rs +++ b/crates/api_crud/src/custom_emoji/delete.rs @@ -8,14 +8,14 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::custom_emoji::CustomEmoji; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn delete_custom_emoji( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs index 5a2631a62..63246f85d 100644 --- a/crates/api_crud/src/custom_emoji/update.rs +++ b/crates/api_crud/src/custom_emoji/update.rs @@ -11,14 +11,14 @@ use lemmy_db_schema::source::{ local_site::LocalSite, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn update_custom_emoji( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 6a61c032b..fcd274c03 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, spawn_try_task, utils::{ slurs::check_slurs, @@ -55,7 +55,7 @@ pub async fn create_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; honeypot_check(&data.honeypot)?; @@ -85,7 +85,9 @@ pub async fn create_post( .await?; let community_id = data.community_id; - let community = Community::read(&mut context.pool(), community_id).await?; + let community = Community::read(&mut context.pool(), community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if community.posting_restricted_to_mods { let community_id = data.community_id; let is_mod = CommunityModeratorView::is_community_moderator( @@ -157,6 +159,7 @@ pub async fn create_post( generate_post_link_metadata( updated_post.clone(), custom_thumbnail, + None, |post| Some(SendActivityData::CreatePost(post)), Some(local_site), context.reset_request_count(), diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index a8fce28fc..696566c8e 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -12,16 +12,18 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn delete_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; // Dont delete it if its already been deleted. if orig_post.deleted == data.deleted { diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index e701008b7..2a567187f 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -14,15 +14,17 @@ use lemmy_db_views::{ structs::{LocalUserView, PostView, SiteView}, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn get_post( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { - let local_site = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let local_site = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -33,15 +35,19 @@ pub async fn get_post( id } else if let Some(comment_id) = data.comment_id { Comment::read(&mut context.pool(), comment_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPost)? + .await? + .ok_or(LemmyErrorType::CouldntFindComment)? .post_id } else { Err(LemmyErrorType::CouldntFindPost)? }; // 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).await?.community_id; + let community_id = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)? + .community_id; + let is_mod_or_admin = is_mod_or_admin_opt( &mut context.pool(), local_user_view.as_ref(), @@ -51,8 +57,8 @@ pub async fn get_post( .is_ok(); let post_view = PostView::read(&mut context.pool(), post_id, person_id, is_mod_or_admin) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindPost)?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; // Mark the post as read let post_id = post_view.post.id; @@ -67,8 +73,8 @@ pub async fn get_post( person_id, is_mod_or_admin, ) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; // Insert into PersonPostAggregates // to update the read_comments count diff --git a/crates/api_crud/src/post/remove.rs b/crates/api_crud/src/post/remove.rs index cbcf069b6..682ed75d3 100644 --- a/crates/api_crud/src/post/remove.rs +++ b/crates/api_crud/src/post/remove.rs @@ -16,16 +16,18 @@ use lemmy_db_schema::{ traits::{Crud, Reportable}, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn remove_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; 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 c08f35307..74e0c0d47 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::check_slurs_opt, validation::{ @@ -45,7 +45,7 @@ pub async fn update_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // TODO No good way to handle a clear. @@ -70,7 +70,9 @@ pub async fn update_post( check_url_scheme(&custom_thumbnail)?; let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; check_community_user_action( &local_user_view.person, @@ -116,6 +118,7 @@ pub async fn update_post( generate_post_link_metadata( updated_post.clone(), custom_thumbnail, + None, |post| Some(SendActivityData::UpdatePost(post)), Some(local_site), context.reset_request_count(), diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 32d8b99e6..e977a6c86 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -24,7 +24,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{markdown::markdown_to_html, validation::is_valid_body_field}, }; @@ -33,7 +33,7 @@ pub async fn create_private_message( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; let slur_regex = local_site_to_slur_regex(&local_site); @@ -76,12 +76,16 @@ pub async fn create_private_message( .await .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?; + let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; // 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?; + let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; 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 ef0864d70..dc028ff41 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -10,17 +10,19 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn delete_private_message( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; if local_user_view.person.id != orig_private_message.creator_id { Err(LemmyErrorType::EditPrivateMessageNotAllowed)? } @@ -45,7 +47,9 @@ pub async fn delete_private_message( ) .await?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; Ok(Json(PrivateMessageResponse { private_message_view: view, })) diff --git a/crates/api_crud/src/private_message/read.rs b/crates/api_crud/src/private_message/read.rs index 9f289c911..7558b97fc 100644 --- a/crates/api_crud/src/private_message/read.rs +++ b/crates/api_crud/src/private_message/read.rs @@ -4,14 +4,14 @@ use lemmy_api_common::{ private_message::{GetPrivateMessages, PrivateMessagesResponse}, }; use lemmy_db_views::{private_message_view::PrivateMessageQuery, structs::LocalUserView}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn get_private_message( data: Query, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_id = local_user_view.person.id; let page = data.page; diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 29063fd10..2842fea65 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -16,7 +16,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::validation::is_valid_body_field, }; @@ -25,12 +25,14 @@ pub async fn update_private_message( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Checking permissions let private_message_id = data.private_message_id; - let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?; + let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; if local_user_view.person.id != orig_private_message.creator_id { Err(LemmyErrorType::EditPrivateMessageNotAllowed)? } @@ -54,7 +56,9 @@ pub async fn update_private_message( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?; - let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; + let view = PrivateMessageView::read(&mut context.pool(), private_message_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPrivateMessage)?; 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 8542117e7..466c7ff1d 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{LemmyErrorType, LemmyResult}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::{ @@ -46,7 +46,7 @@ pub async fn create_site( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin; other types of users should not create site data... @@ -129,7 +129,9 @@ pub async fn create_site( LocalSiteRateLimit::update(&mut context.pool(), &local_site_rate_limit_form).await?; - let site_view = SiteView::read_local(&mut context.pool()).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?; diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index 0d3685a94..69e82007c 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -19,8 +19,8 @@ use lemmy_db_views_actor::structs::{ PersonView, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, - CACHE_DURATION_SHORT, + error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, + CACHE_DURATION_API, VERSION, }; use moka::future::Cache; @@ -30,18 +30,20 @@ use once_cell::sync::Lazy; pub async fn get_site( local_user_view: Option, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { static CACHE: Lazy> = Lazy::new(|| { Cache::builder() .max_capacity(1) - .time_to_live(CACHE_DURATION_SHORT) + .time_to_live(CACHE_DURATION_API) .build() }); // 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?; + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; 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?; diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 530dbb47f..7b44b92a6 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::check_slurs_opt, validation::{ @@ -51,8 +51,10 @@ pub async fn update_site( data: Json, context: Data, local_user_view: LocalUserView, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let local_site = site_view.local_site; let site = site_view.site; @@ -181,7 +183,9 @@ pub async fn update_site( 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 site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index d24a287db..6640e9e53 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -31,7 +31,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ slurs::{check_slurs, check_slurs_opt}, validation::is_valid_actor_name, @@ -44,8 +44,10 @@ pub async fn register( data: Json, req: HttpRequest, context: Data, -) -> Result, LemmyError> { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let local_site = site_view.local_site; let require_registration_application = local_site.registration_mode == RegistrationMode::RequireApplication; diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index a2a1f25bf..f68301be1 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -39,7 +39,7 @@ use lemmy_db_schema::{ }, traits::{Bannable, Crud, Followable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl BlockUser { @@ -51,7 +51,7 @@ impl BlockUser { reason: Option, expires: Option>, context: &Data, - ) -> Result { + ) -> LemmyResult { let audience = if let SiteOrCommunity::Community(c) = target { Some(c.id().into()) } else { @@ -85,7 +85,7 @@ impl BlockUser { reason: Option, expires: Option>, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let block = BlockUser::new( target, user, @@ -125,7 +125,7 @@ impl ActivityHandler for BlockUser { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; match self.target.dereference(context).await? { SiteOrCommunity::Site(site) => { @@ -148,7 +148,7 @@ impl ActivityHandler for BlockUser { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let expires = self.expires.or(self.end_time).map(Into::into); let mod_person = self.actor.dereference(context).await?; diff --git a/crates/apub/src/activities/block/mod.rs b/crates/apub/src/activities/block/mod.rs index 92fea4ec5..ced50e2de 100644 --- a/crates/apub/src/activities/block/mod.rs +++ b/crates/apub/src/activities/block/mod.rs @@ -23,7 +23,10 @@ use lemmy_db_schema::{ utils::DbPool, }; use lemmy_db_views::structs::SiteView; -use lemmy_utils::error::{LemmyError, LemmyResult}; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use serde::Deserialize; use url::Url; @@ -58,10 +61,7 @@ impl Object for SiteOrCommunity { } #[tracing::instrument(skip_all)] - async fn read_from_id( - object_id: Url, - data: &Data, - ) -> Result, LemmyError> + async fn read_from_id(object_id: Url, data: &Data) -> LemmyResult> where Self: Sized, { @@ -74,11 +74,11 @@ impl Object for SiteOrCommunity { }) } - async fn delete(self, _data: &Data) -> Result<(), LemmyError> { + async fn delete(self, _data: &Data) -> LemmyResult<()> { unimplemented!() } - async fn into_json(self, _data: &Data) -> Result { + async fn into_json(self, _data: &Data) -> LemmyResult { unimplemented!() } @@ -87,7 +87,7 @@ impl Object for SiteOrCommunity { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match apub { InstanceOrGroup::Instance(i) => ApubSite::verify(i, expected_domain, data).await, InstanceOrGroup::Group(g) => ApubCommunity::verify(g, expected_domain, data).await, @@ -95,7 +95,7 @@ impl Object for SiteOrCommunity { } #[tracing::instrument(skip_all)] - async fn from_json(apub: Self::Kind, data: &Data) -> Result + async fn from_json(apub: Self::Kind, data: &Data) -> LemmyResult where Self: Sized, { @@ -117,10 +117,7 @@ impl SiteOrCommunity { } } -async fn generate_cc( - target: &SiteOrCommunity, - pool: &mut DbPool<'_>, -) -> Result, LemmyError> { +async fn generate_cc(target: &SiteOrCommunity, pool: &mut DbPool<'_>) -> LemmyResult> { Ok(match target { SiteOrCommunity::Site(_) => Site::read_remote_sites(pool) .await? @@ -139,8 +136,14 @@ pub(crate) async fn send_ban_from_site( ban: bool, expires: Option, context: Data, -) -> Result<(), LemmyError> { - let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into()); +) -> LemmyResult<()> { + let site = SiteOrCommunity::Site( + SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)? + .site + .into(), + ); let expires = check_expire_time(expires)?; // if the action affects a local user, federate to other instances @@ -180,6 +183,7 @@ 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)?; diff --git a/crates/apub/src/activities/block/undo_block_user.rs b/crates/apub/src/activities/block/undo_block_user.rs index 756d0a149..b92320b2d 100644 --- a/crates/apub/src/activities/block/undo_block_user.rs +++ b/crates/apub/src/activities/block/undo_block_user.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ }, traits::{Bannable, Crud}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl UndoBlockUser { @@ -38,7 +38,7 @@ impl UndoBlockUser { mod_: &ApubPerson, reason: Option, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?; let audience = if let SiteOrCommunity::Community(c) = target { Some(c.id().into()) @@ -88,7 +88,7 @@ impl ActivityHandler for UndoBlockUser { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; verify_domains_match(self.actor.inner(), self.object.actor.inner())?; self.object.verify(context).await?; @@ -96,7 +96,7 @@ impl ActivityHandler for UndoBlockUser { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let expires = self.object.expires.or(self.object.end_time).map(Into::into); let mod_person = self.actor.dereference(context).await?; diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 474afc3b9..9a3928882 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -82,7 +82,7 @@ impl AnnounceActivity { object: RawAnnouncableActivities, community: &ApubCommunity, context: &Data, - ) -> Result { + ) -> LemmyResult { let inner_kind = object .other .get("type") @@ -105,7 +105,7 @@ impl AnnounceActivity { object: RawAnnouncableActivities, community: &ApubCommunity, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let announce = AnnounceActivity::new(object.clone(), community, context)?; let inboxes = ActivitySendTargets::to_local_community_followers(community.id); send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; @@ -148,13 +148,13 @@ impl ActivityHandler for AnnounceActivity { } #[tracing::instrument(skip_all)] - async fn verify(&self, _context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, _context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; Ok(()) } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let object: AnnouncableActivities = self.object.object(context).await?.try_into()?; diff --git a/crates/apub/src/activities/community/collection_add.rs b/crates/apub/src/activities/community/collection_add.rs index fdd62bdb1..3d5de4128 100644 --- a/crates/apub/src/activities/community/collection_add.rs +++ b/crates/apub/src/activities/community/collection_add.rs @@ -36,7 +36,10 @@ use lemmy_db_schema::{ }, traits::{Crud, Joinable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use url::Url; impl CollectionAdd { @@ -46,7 +49,7 @@ impl CollectionAdd { added_mod: &ApubPerson, actor: &ApubPerson, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let id = generate_activity_id( AddType::Add, &context.settings().get_protocol_and_hostname(), @@ -72,7 +75,7 @@ impl CollectionAdd { featured_post: &ApubPost, actor: &ApubPerson, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let id = generate_activity_id( AddType::Add, &context.settings().get_protocol_and_hostname(), @@ -114,7 +117,7 @@ impl ActivityHandler for CollectionAdd { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; @@ -123,10 +126,12 @@ impl ActivityHandler for CollectionAdd { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + 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?; + Community::get_by_collection_url(&mut context.pool(), &self.target.into()) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; match collection_type { CollectionType::Moderators => { let new_mod = ObjectId::::from(self.object) @@ -179,13 +184,15 @@ pub(crate) async fn send_add_mod_to_community( updated_mod_id: PersonId, added: bool, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { 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 @@ -199,11 +206,12 @@ pub(crate) async fn send_feature_post( actor: Person, featured: bool, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let actor: ApubPerson = actor.into(); 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 06238a890..634ca526c 100644 --- a/crates/apub/src/activities/community/collection_remove.rs +++ b/crates/apub/src/activities/community/collection_remove.rs @@ -31,7 +31,10 @@ use lemmy_db_schema::{ }, traits::{Crud, Joinable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use url::Url; impl CollectionRemove { @@ -41,7 +44,7 @@ impl CollectionRemove { removed_mod: &ApubPerson, actor: &ApubPerson, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let id = generate_activity_id( RemoveType::Remove, &context.settings().get_protocol_and_hostname(), @@ -67,7 +70,7 @@ impl CollectionRemove { featured_post: &ApubPost, actor: &ApubPerson, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let id = generate_activity_id( RemoveType::Remove, &context.settings().get_protocol_and_hostname(), @@ -109,7 +112,7 @@ impl ActivityHandler for CollectionRemove { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; @@ -118,10 +121,12 @@ impl ActivityHandler for CollectionRemove { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + 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?; + Community::get_by_collection_url(&mut context.pool(), &self.target.into()) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; 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 db9dfa36c..bafb42a4a 100644 --- a/crates/apub/src/activities/community/lock_page.rs +++ b/crates/apub/src/activities/community/lock_page.rs @@ -31,7 +31,10 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use url::Url; #[async_trait::async_trait] @@ -106,9 +109,10 @@ pub(crate) async fn send_lock_post( actor: Person, locked: bool, context: Data, -) -> Result<(), LemmyError> { +) -> 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/mod.rs b/crates/apub/src/activities/community/mod.rs index 612fd8a7a..a47dec2bd 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -10,7 +10,7 @@ use lemmy_db_schema::{ source::{activity::ActivitySendTargets, person::PersonFollower}, CommunityVisibility, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub mod announce; pub mod collection_add; @@ -39,7 +39,7 @@ pub(crate) async fn send_activity_in_community( extra_inboxes: ActivitySendTargets, is_mod_action: bool, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { // If community is local only, don't send anything out if community.visibility != CommunityVisibility::Public { return Ok(()); diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index 6b1fce066..d1bec0b75 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -29,7 +29,10 @@ use lemmy_db_schema::{ }, traits::{Crud, Reportable}, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use url::Url; impl Report { @@ -40,7 +43,7 @@ impl Report { community: Community, reason: String, context: Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let actor: ApubPerson = actor.into(); let community: ApubCommunity = community.into(); let kind = FlagType::Flag; @@ -67,7 +70,9 @@ 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?; + let object_creator = Person::read(&mut context.pool(), object_creator_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let object_creator_site: Option = Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id) .await? @@ -94,14 +99,14 @@ impl ActivityHandler for Report { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; Ok(()) } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let actor = self.actor.dereference(context).await?; let reason = self.reason()?; diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index ca293d002..3457e6c24 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -26,14 +26,14 @@ use lemmy_db_schema::{ traits::Crud, utils::naive_now, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; pub(crate) async fn send_update_community( community: Community, actor: Person, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let community: ApubCommunity = community.into(); let actor: ApubPerson = actor.into(); let id = generate_activity_id( @@ -76,7 +76,7 @@ impl ActivityHandler for UpdateCommunity { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; @@ -86,7 +86,7 @@ impl ActivityHandler for UpdateCommunity { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let community = self.community(context).await?; diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index c8d9a017e..7f1532087 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -39,7 +39,11 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_utils::{error::LemmyError, utils::mention::scrape_text_for_mentions}; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + utils::mention::scrape_text_for_mentions, + LemmyErrorType, +}; use url::Url; impl CreateOrUpdateNote { @@ -49,14 +53,20 @@ impl CreateOrUpdateNote { person_id: PersonId, kind: CreateOrUpdateType, context: Data, - ) -> Result<(), LemmyError> { + ) -> 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?; + let post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; let community_id = post.community_id; - let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into(); + let person: ApubPerson = Person::read(&mut context.pool(), person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? + .into(); let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let id = generate_activity_id( @@ -114,7 +124,7 @@ impl ActivityHandler for CreateOrUpdateNote { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; let post = self.object.get_parents(context).await?.0; let community = self.community(context).await?; @@ -129,7 +139,7 @@ impl ActivityHandler for CreateOrUpdateNote { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; // Need to do this check here instead of Note::from_json because we need the person who // send the activity, not the comment author. @@ -159,8 +169,6 @@ impl ActivityHandler for CreateOrUpdateNote { CommentAggregates::update_hot_rank(&mut context.pool(), comment.id).await?; let do_send_email = self.kind == CreateOrUpdateType::Create; - let post_id = comment.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; let actor = self.actor.dereference(context).await?; // Note: @@ -169,7 +177,7 @@ impl ActivityHandler for CreateOrUpdateNote { // anyway. // TODO: for compatibility with other projects, it would be much better to read this from cc or tags let mentions = scrape_text_for_mentions(&comment.content); - send_local_notifs(mentions, &comment.0, &actor, &post, do_send_email, context).await?; + send_local_notifs(mentions, comment.id, &actor, do_send_email, context).await?; Ok(()) } } diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 8b2fdbdb4..2ca7e52cc 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -33,7 +33,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Likeable}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use url::Url; impl CreateOrUpdatePage { @@ -43,7 +43,7 @@ impl CreateOrUpdatePage { community: &ApubCommunity, kind: CreateOrUpdateType, context: &Data, - ) -> Result { + ) -> LemmyResult { let id = generate_activity_id( kind.clone(), &context.settings().get_protocol_and_hostname(), @@ -65,12 +65,16 @@ impl CreateOrUpdatePage { person_id: PersonId, kind: CreateOrUpdateType, context: Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let post = ApubPost(post); let community_id = post.community_id; - let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into(); + let person: ApubPerson = Person::read(&mut context.pool(), person_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? + .into(); let community: ApubCommunity = Community::read(&mut context.pool(), community_id) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); let create_or_update = @@ -104,7 +108,7 @@ impl ActivityHandler for CreateOrUpdatePage { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; @@ -138,7 +142,7 @@ impl ActivityHandler for CreateOrUpdatePage { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let post = ApubPost::from_json(self.object, context).await?; diff --git a/crates/apub/src/activities/create_or_update/private_message.rs b/crates/apub/src/activities/create_or_update/private_message.rs index f3ea3ca26..950f4ae99 100644 --- a/crates/apub/src/activities/create_or_update/private_message.rs +++ b/crates/apub/src/activities/create_or_update/private_message.rs @@ -15,14 +15,14 @@ use activitypub_federation::{ use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::activity::ActivitySendTargets; use lemmy_db_views::structs::PrivateMessageView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; pub(crate) async fn send_create_or_update_pm( pm_view: PrivateMessageView, kind: CreateOrUpdateType, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let actor: ApubPerson = pm_view.creator.into(); let recipient: ApubPerson = pm_view.recipient.into(); @@ -57,7 +57,7 @@ impl ActivityHandler for CreateOrUpdateChatMessage { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_person(&self.actor, context).await?; verify_domains_match(self.actor.inner(), self.object.id.inner())?; verify_domains_match(self.to[0].inner(), self.object.to[0].inner())?; @@ -66,7 +66,7 @@ impl ActivityHandler for CreateOrUpdateChatMessage { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; ApubPrivateMessage::from_json(self.object, context).await?; Ok(()) diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 18f8cf6fb..cecc051b4 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -27,7 +27,7 @@ use lemmy_db_schema::{ }, traits::{Crud, Reportable}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use url::Url; #[async_trait::async_trait] @@ -44,13 +44,13 @@ impl ActivityHandler for Delete { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_delete_activity(self, self.summary.is_some(), context).await?; Ok(()) } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; if let Some(reason) = self.summary { // We set reason to empty string if it doesn't exist, to distinguish between delete and @@ -88,7 +88,7 @@ impl Delete { community: Option<&Community>, summary: Option, context: &Data, - ) -> Result { + ) -> LemmyResult { let id = generate_activity_id( DeleteType::Delete, &context.settings().get_protocol_and_hostname(), @@ -114,7 +114,7 @@ pub(in crate::activities) async fn receive_remove_action( object: &Url, reason: Option, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index 03f01a3b9..c9d268e74 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use std::ops::Deref; use url::Url; @@ -56,7 +56,7 @@ pub(crate) async fn send_apub_delete_in_community( reason: Option, deleted: bool, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let actor = ApubPerson::from(actor); let is_mod_action = reason.is_some(); let activity = if deleted { @@ -83,10 +83,11 @@ pub(crate) async fn send_apub_delete_private_message( pm: PrivateMessage, deleted: bool, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { 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()); @@ -105,7 +106,7 @@ pub async fn send_apub_delete_user( person: Person, remove_data: bool, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let person: ApubPerson = person.into(); let deletable = DeletableObjects::Person(person.clone()); @@ -131,7 +132,7 @@ impl DeletableObjects { pub(crate) async fn read_from_db( ap_id: &Url, context: &Data, - ) -> Result { + ) -> LemmyResult { if let Some(c) = ApubCommunity::read_from_id(ap_id.clone(), context).await? { return Ok(DeletableObjects::Community(c)); } @@ -166,7 +167,7 @@ pub(in crate::activities) async fn verify_delete_activity( activity: &Delete, is_mod_action: bool, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let object = DeletableObjects::read_from_db(activity.object.id(), context).await?; match object { DeletableObjects::Community(community) => { @@ -221,7 +222,7 @@ async fn verify_delete_post_or_comment( community: &ApubCommunity, is_mod_action: bool, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { verify_person_in_community(actor, community, context).await?; if is_mod_action { verify_mod_action(actor, community, context).await?; @@ -240,7 +241,7 @@ async fn receive_delete_action( deleted: bool, do_purge_user_account: Option, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 558d72b57..4f0fad670 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -25,7 +25,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use url::Url; #[async_trait::async_trait] @@ -48,7 +48,7 @@ impl ActivityHandler for UndoDelete { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; if self.object.summary.is_some() { UndoDelete::receive_undo_remove_action( @@ -72,7 +72,7 @@ impl UndoDelete { community: Option<&Community>, summary: Option, context: &Data, - ) -> Result { + ) -> LemmyResult { let object = Delete::new(actor, object, to.clone(), community, summary, context)?; let id = generate_activity_id( @@ -96,7 +96,7 @@ impl UndoDelete { actor: &ApubPerson, object: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match DeletableObjects::read_from_db(object, context).await? { DeletableObjects::Community(community) => { if community.local { diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index efecef7f1..fa711b904 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -14,12 +14,12 @@ use lemmy_db_schema::{ source::{activity::ActivitySendTargets, community::CommunityFollower}, traits::Followable, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl AcceptFollow { #[tracing::instrument(skip_all)] - pub async fn send(follow: Follow, context: &Data) -> Result<(), LemmyError> { + pub async fn send(follow: Follow, context: &Data) -> LemmyResult<()> { let user_or_community = follow.object.dereference_local(context).await?; let person = follow.actor.clone().dereference(context).await?; let accept = AcceptFollow { @@ -52,7 +52,7 @@ impl ActivityHandler for AcceptFollow { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_urls_match(self.actor.inner(), self.object.object.inner())?; self.object.verify(context).await?; if let Some(to) = &self.to { @@ -62,7 +62,7 @@ impl ActivityHandler for AcceptFollow { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let community = self.actor.dereference(context).await?; let person = self.object.actor.dereference(context).await?; diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 6b954ebf2..97227835a 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -26,7 +26,7 @@ use lemmy_db_schema::{ traits::Followable, CommunityVisibility, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use url::Url; impl Follow { @@ -34,7 +34,7 @@ impl Follow { actor: &ApubPerson, community: &ApubCommunity, context: &Data, - ) -> Result { + ) -> LemmyResult { Ok(Follow { actor: actor.id().into(), object: community.id().into(), @@ -52,7 +52,7 @@ impl Follow { actor: &ApubPerson, community: &ApubCommunity, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let follow = Follow::new(actor, community, context)?; let inbox = if community.local { ActivitySendTargets::empty() @@ -77,7 +77,7 @@ impl ActivityHandler for Follow { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_person(&self.actor, context).await?; let object = self.object.dereference(context).await?; if let UserOrCommunity::Community(c) = object { @@ -90,7 +90,7 @@ impl ActivityHandler for Follow { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let actor = self.actor.dereference(context).await?; let object = self.object.dereference(context).await?; diff --git a/crates/apub/src/activities/following/mod.rs b/crates/apub/src/activities/following/mod.rs index c4f0bd0b7..7c7163f12 100644 --- a/crates/apub/src/activities/following/mod.rs +++ b/crates/apub/src/activities/following/mod.rs @@ -5,7 +5,7 @@ use crate::{ use activitypub_federation::config::Data; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::{community::Community, person::Person}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub mod accept; pub mod follow; @@ -16,7 +16,7 @@ pub async fn send_follow_community( person: Person, follow: bool, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let community: ApubCommunity = community.into(); let actor: ApubPerson = person.into(); if follow { diff --git a/crates/apub/src/activities/following/undo_follow.rs b/crates/apub/src/activities/following/undo_follow.rs index 90dd452f2..ba6253946 100644 --- a/crates/apub/src/activities/following/undo_follow.rs +++ b/crates/apub/src/activities/following/undo_follow.rs @@ -20,7 +20,7 @@ use lemmy_db_schema::{ }, traits::Followable, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl UndoFollow { @@ -29,7 +29,7 @@ impl UndoFollow { actor: &ApubPerson, community: &ApubCommunity, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let object = Follow::new(actor, community, context)?; let undo = UndoFollow { actor: actor.id().into(), @@ -64,7 +64,7 @@ impl ActivityHandler for UndoFollow { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { verify_urls_match(self.actor.inner(), self.object.actor.inner())?; verify_person(&self.actor, context).await?; self.object.verify(context).await?; @@ -75,7 +75,7 @@ impl ActivityHandler for UndoFollow { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let person = self.actor.dereference(context).await?; let object = self.object.object.dereference(context).await?; diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 1472d6866..d81e7cabf 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -61,7 +61,7 @@ pub mod voting; async fn verify_person( person_id: &ObjectId, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let person = person_id.dereference(context).await?; if person.banned { Err(anyhow!("Person {} is banned", person_id)) @@ -78,7 +78,7 @@ pub(crate) async fn verify_person_in_community( person_id: &ObjectId, community: &ApubCommunity, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let person = person_id.dereference(context).await?; if person.banned { Err(LemmyErrorType::PersonIsBannedFromSite( @@ -105,7 +105,7 @@ pub(crate) async fn verify_mod_action( mod_id: &ObjectId, community: &Community, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let mod_ = mod_id.dereference(context).await?; let is_mod_or_admin = @@ -124,7 +124,7 @@ pub(crate) async fn verify_mod_action( Err(LemmyErrorType::NotAModerator)? } -pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> { +pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { if ![to, cc].iter().any(|set| set.contains(&public())) { Err(LemmyErrorType::ObjectIsNotPublic)? } else { @@ -132,10 +132,7 @@ pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> } } -pub(crate) fn verify_community_matches( - a: &ObjectId, - b: T, -) -> Result<(), LemmyError> +pub(crate) fn verify_community_matches(a: &ObjectId, b: T) -> LemmyResult<()> where T: Into>, { @@ -147,7 +144,7 @@ where } } -pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> { +pub(crate) fn check_community_deleted_or_removed(community: &Community) -> LemmyResult<()> { if community.deleted || community.removed { Err(LemmyErrorType::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)? } else { @@ -196,7 +193,7 @@ async fn send_lemmy_activity( actor: &ActorT, send_targets: ActivitySendTargets, sensitive: bool, -) -> Result<(), LemmyError> +) -> LemmyResult<()> where Activity: ActivityHandler + Serialize + Send + Sync + Clone, ActorT: Actor + GetActorType, @@ -248,7 +245,9 @@ 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?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; send_apub_delete_in_community( person, community, @@ -265,7 +264,9 @@ pub async fn match_outgoing_activities( reason, removed, } => { - let community = Community::read(&mut context.pool(), post.community_id).await?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; send_apub_delete_in_community( moderator, community, diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 0f9876f1a..3e59cb7d0 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -21,7 +21,7 @@ use lemmy_db_schema::{ }, traits::Likeable, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; pub mod undo_vote; pub mod vote; @@ -32,7 +32,7 @@ pub(crate) async fn send_like_activity( community: Community, score: i16, context: Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let object_id: ObjectId = object_id.into(); let actor: ApubPerson = actor.into(); let community: ApubCommunity = community.into(); @@ -44,7 +44,7 @@ pub(crate) async fn send_like_activity( let activity = AnnouncableActivities::Vote(vote); send_activity_in_community(activity, &actor, &community, empty, false, &context).await } else { - // Lemmy API doesnt distinguish between Undo/Like and Undo/Dislike, so we hardcode it here. + // Lemmy API doesn't distinguish between Undo/Like and Undo/Dislike, so we hardcode it here. let vote = Vote::new(object_id, &actor, &community, VoteType::Like, &context)?; let undo_vote = UndoVote::new(vote, &actor, &community, &context)?; let activity = AnnouncableActivities::UndoVote(undo_vote); @@ -58,7 +58,7 @@ async fn vote_comment( actor: ApubPerson, comment: &ApubComment, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, @@ -78,7 +78,7 @@ async fn vote_post( actor: ApubPerson, post: &ApubPost, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let post_id = post.id; let like_form = PostLikeForm { post_id: post.id, @@ -96,7 +96,7 @@ async fn undo_vote_comment( actor: ApubPerson, comment: &ApubComment, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let comment_id = comment.id; let person_id = actor.id; CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; @@ -108,7 +108,7 @@ async fn undo_vote_post( actor: ApubPerson, post: &ApubPost, context: &Data, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let post_id = post.id; let person_id = actor.id; PostLike::remove(&mut context.pool(), person_id, post_id).await?; diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index f83055620..61875d442 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -19,7 +19,7 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl UndoVote { @@ -28,7 +28,7 @@ impl UndoVote { actor: &ApubPerson, community: &ApubCommunity, context: &Data, - ) -> Result { + ) -> LemmyResult { Ok(UndoVote { actor: actor.id().into(), object: vote, @@ -56,7 +56,7 @@ impl ActivityHandler for UndoVote { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; verify_urls_match(self.actor.inner(), self.object.actor.inner())?; @@ -65,7 +65,7 @@ impl ActivityHandler for UndoVote { } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let actor = self.actor.dereference(context).await?; let object = self.object.object.dereference(context).await?; diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 5625ea6ba..324c8b300 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -19,7 +19,7 @@ use activitypub_federation::{ }; use lemmy_api_common::{context::LemmyContext, utils::check_bot_account}; use lemmy_db_schema::source::local_site::LocalSite; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; impl Vote { @@ -29,7 +29,7 @@ impl Vote { community: &ApubCommunity, kind: VoteType, context: &Data, - ) -> Result { + ) -> LemmyResult { Ok(Vote { actor: actor.id().into(), object: object_id, @@ -54,14 +54,14 @@ impl ActivityHandler for Vote { } #[tracing::instrument(skip_all)] - async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + async fn verify(&self, context: &Data) -> LemmyResult<()> { let community = self.community(context).await?; verify_person_in_community(&self.actor, &community, context).await?; Ok(()) } #[tracing::instrument(skip_all)] - async fn receive(self, context: &Data) -> Result<(), LemmyError> { + async fn receive(self, context: &Data) -> LemmyResult<()> { insert_received_activity(&self.id, context).await?; let actor = self.actor.dereference(context).await?; let object = self.object.dereference(context).await?; diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 7b33499c8..3aeb7e45e 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -26,7 +26,7 @@ use crate::{ }; use activitypub_federation::{config::Data, traits::ActivityHandler}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -101,7 +101,7 @@ pub enum AnnouncableActivities { #[async_trait::async_trait] impl InCommunity for AnnouncableActivities { #[tracing::instrument(skip(self, context))] - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { use AnnouncableActivities::*; match self { CreateOrUpdateComment(a) => a.community(context).await, diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index c83756f54..25d197007 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -15,14 +15,14 @@ use lemmy_db_schema::{ traits::Crud, }; use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn list_comments( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site)?; @@ -58,7 +58,12 @@ pub async fn list_comments( // 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?.path) + Some( + Comment::read(&mut context.pool(), parent_id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)? + .path, + ) } else { None }; diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index 384f1b60e..ec5412de8 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -15,15 +15,17 @@ use lemmy_db_views::{ post_view::PostQuery, structs::{LocalUserView, PaginationCursor, SiteView}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn list_posts( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { - let local_site = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let local_site = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; check_private_instance(&local_user_view, &local_site.local_site)?; diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs index a41deb32c..dae7719ae 100644 --- a/crates/apub/src/api/read_community.rs +++ b/crates/apub/src/api/read_community.rs @@ -13,14 +13,14 @@ use lemmy_db_schema::source::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn get_community( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; if data.name.is_none() && data.id.is_none() { @@ -56,8 +56,8 @@ pub async fn get_community( person_id, is_mod_or_admin, ) - .await - .with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?; + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id) .await diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index c779657c8..0d65ab4f7 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -13,20 +13,22 @@ use lemmy_db_views::{ structs::{LocalUserView, SiteView}, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt2, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn read_person( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { // Check to make sure a person name or an id is given if data.username.is_none() && data.person_id.is_none() { Err(LemmyErrorType::NoIdGiven)? } - let local_site = SiteView::read_local(&mut context.pool()).await?; + let local_site = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; check_private_instance(&local_user_view, &local_site.local_site)?; @@ -46,7 +48,9 @@ pub async fn read_person( // 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?; + let person_view = PersonView::read(&mut context.pool(), person_details_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let sort = data.sort; let page = data.page; diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index 6d672a8cd..47f6c5d06 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -13,14 +13,14 @@ use lemmy_api_common::{ use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool}; use lemmy_db_views::structs::{CommentView, LocalUserView, PostView}; use lemmy_db_views_actor::structs::{CommunityView, PersonView}; -use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorExt2, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn resolve_object( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { +) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; check_private_instance(&local_user_view, &local_site)?; let person_id = local_user_view.map(|v| v.person.id); @@ -46,27 +46,43 @@ async fn convert_response( object: SearchableObjects, user_id: Option, pool: &mut DbPool<'_>, -) -> Result, LemmyError> { +) -> LemmyResult> { use SearchableObjects::*; let removed_or_deleted; let mut res = ResolveObjectResponse::default(); match object { Post(p) => { removed_or_deleted = p.deleted || p.removed; - res.post = Some(PostView::read(pool, p.id, user_id, false).await?) + res.post = Some( + PostView::read(pool, p.id, user_id, false) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?, + ) } Comment(c) => { removed_or_deleted = c.deleted || c.removed; - res.comment = Some(CommentView::read(pool, c.id, user_id).await?) + res.comment = Some( + CommentView::read(pool, c.id, user_id) + .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?) + res.person = Some( + PersonView::read(pool, u.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?, + ) } UserOrCommunity::Community(c) => { removed_or_deleted = c.deleted || c.removed; - res.community = Some(CommunityView::read(pool, c.id, user_id, false).await?) + res.community = Some( + CommunityView::read(pool, c.id, user_id, false) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?, + ) } }, }; diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 32128a3c1..f3cd36faf 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -13,15 +13,17 @@ use lemmy_db_views::{ structs::{LocalUserView, SiteView}, }; use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[tracing::instrument(skip(context))] pub async fn search( data: Query, context: Data, local_user_view: Option, -) -> Result, LemmyError> { - let local_site = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult> { + let local_site = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; check_private_instance(&local_user_view, &local_site.local_site)?; diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 4c1edcbff..57e1d0f97 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -26,7 +26,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}, + error::{LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}, spawn_try_task, }; use serde::{Deserialize, Serialize}; @@ -70,7 +70,7 @@ pub struct UserSettingsBackup { pub async fn export_settings( local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { let lists = LocalUser::export_backup(&mut context.pool(), local_user_view.person.id).await?; let vec_into = |vec: Vec<_>| vec.into_iter().map(Into::into).collect(); @@ -97,7 +97,7 @@ pub async fn import_settings( data: Json, local_user_view: LocalUserView, context: Data, -) -> Result, LemmyError> { +) -> LemmyResult> { let person_form = PersonUpdateForm { display_name: Some(data.display_name.clone()), bio: Some(data.bio.clone()), @@ -363,7 +363,11 @@ mod tests { .build(); let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?; - Ok(LocalUserView::read(&mut context.pool(), local_user.id).await?) + Ok( + LocalUserView::read(&mut context.pool(), local_user.id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?, + ) } #[tokio::test] @@ -396,8 +400,9 @@ mod tests { // 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?; + let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; assert_eq!( export_user.person.display_name, diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 0532d0aef..02b912f44 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ traits::Joinable, }; use lemmy_db_views_actor::structs::CommunityModeratorView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; #[derive(Clone, Debug)] @@ -29,10 +29,7 @@ impl Collection for ApubCommunityModerators { type Error = LemmyError; #[tracing::instrument(skip_all)] - async fn read_local( - owner: &Self::Owner, - data: &Data, - ) -> Result { + async fn read_local(owner: &Self::Owner, data: &Data) -> LemmyResult { let moderators = CommunityModeratorView::for_community(&mut data.pool(), owner.id).await?; let ordered_items = moderators .into_iter() @@ -50,7 +47,7 @@ impl Collection for ApubCommunityModerators { group_moderators: &GroupModerators, expected_domain: &Url, _data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { verify_domains_match(&group_moderators.id, expected_domain)?; Ok(()) } @@ -60,7 +57,7 @@ impl Collection for ApubCommunityModerators { apub: Self::Kind, owner: &Self::Owner, data: &Data, - ) -> Result { + ) -> LemmyResult { let community_id = owner.id; let current_moderators = CommunityModeratorView::for_community(&mut data.pool(), community_id).await?; @@ -118,7 +115,6 @@ mod tests { }, traits::Crud, }; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs index 0799db789..71985f946 100644 --- a/crates/apub/src/collections/community_outbox.rs +++ b/crates/apub/src/collections/community_outbox.rs @@ -23,7 +23,10 @@ use lemmy_db_schema::{ traits::Crud, utils::FETCH_LIMIT_MAX, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use url::Url; #[derive(Clone, Debug)] @@ -37,10 +40,7 @@ impl Collection for ApubCommunityOutbox { type Error = LemmyError; #[tracing::instrument(skip_all)] - async fn read_local( - owner: &Self::Owner, - data: &Data, - ) -> Result { + async fn read_local(owner: &Self::Owner, data: &Data) -> LemmyResult { let post_list: Vec = Post::list_for_community(&mut data.pool(), owner.id) .await? .into_iter() @@ -50,6 +50,7 @@ impl Collection for ApubCommunityOutbox { for post in post_list { let person = Person::read(&mut data.pool(), post.creator_id) .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? .into(); let create = CreateOrUpdatePage::new(post, &person, owner, CreateOrUpdateType::Create, data).await?; @@ -71,7 +72,7 @@ impl Collection for ApubCommunityOutbox { group_outbox: &GroupOutbox, expected_domain: &Url, _data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { verify_domains_match(expected_domain, &group_outbox.id)?; Ok(()) } @@ -81,7 +82,7 @@ impl Collection for ApubCommunityOutbox { apub: Self::Kind, _owner: &Self::Owner, data: &Data, - ) -> Result { + ) -> LemmyResult { let mut outbox_activities = apub.ordered_items; if outbox_activities.len() as i64 > FETCH_LIMIT_MAX { outbox_activities = outbox_activities diff --git a/crates/apub/src/fetcher/mod.rs b/crates/apub/src/fetcher/mod.rs index 4e30b4b16..68fc07d30 100644 --- a/crates/apub/src/fetcher/mod.rs +++ b/crates/apub/src/fetcher/mod.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::traits::ApubActor; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; pub mod post_or_comment; pub mod search; @@ -25,7 +25,7 @@ pub async fn resolve_actor_identifier( context: &Data, local_user_view: &Option, include_deleted: bool, -) -> Result +) -> LemmyResult where ActorType: Object + Object @@ -42,9 +42,12 @@ where .splitn(2, '@') .collect_tuple() .expect("invalid query"); - let actor = DbActor::read_from_name_and_domain(&mut context.pool(), name, domain).await; - if actor.is_ok() { - Ok(actor?.into()) + let actor = DbActor::read_from_name_and_domain(&mut context.pool(), name, domain) + .await + .ok() + .flatten(); + if let Some(actor) = actor { + Ok(actor.into()) } else if local_user_view.is_some() { // Fetch the actor from its home instance using webfinger let actor: ActorType = webfinger_resolve_actor(&identifier.to_lowercase(), context).await?; @@ -59,6 +62,7 @@ where Ok( DbActor::read_from_name(&mut context.pool(), &identifier, include_deleted) .await? + .ok_or(NotFound)? .into(), ) } diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs index 31c53864a..083369b9d 100644 --- a/crates/apub/src/fetcher/post_or_comment.rs +++ b/crates/apub/src/fetcher/post_or_comment.rs @@ -12,7 +12,10 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + LemmyErrorType, +}; use serde::Deserialize; use url::Url; @@ -40,10 +43,7 @@ impl Object for PostOrComment { } #[tracing::instrument(skip_all)] - async fn read_from_id( - object_id: Url, - data: &Data, - ) -> Result, LemmyError> { + async fn read_from_id(object_id: Url, data: &Data) -> LemmyResult> { let post = ApubPost::read_from_id(object_id.clone(), data).await?; Ok(match post { Some(o) => Some(PostOrComment::Post(o)), @@ -54,14 +54,14 @@ impl Object for PostOrComment { } #[tracing::instrument(skip_all)] - async fn delete(self, data: &Data) -> Result<(), LemmyError> { + async fn delete(self, data: &Data) -> LemmyResult<()> { match self { PostOrComment::Post(p) => p.delete(data).await, PostOrComment::Comment(c) => c.delete(data).await, } } - async fn into_json(self, _data: &Data) -> Result { + async fn into_json(self, _data: &Data) -> LemmyResult { unimplemented!() } @@ -70,7 +70,7 @@ impl Object for PostOrComment { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match apub { PageOrNote::Page(a) => ApubPost::verify(a, expected_domain, data).await, PageOrNote::Note(a) => ApubComment::verify(a, expected_domain, data).await, @@ -78,7 +78,7 @@ impl Object for PostOrComment { } #[tracing::instrument(skip_all)] - async fn from_json(apub: PageOrNote, context: &Data) -> Result { + 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?), @@ -88,15 +88,21 @@ impl Object for PostOrComment { #[async_trait::async_trait] impl InCommunity for PostOrComment { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let cid = match self { PostOrComment::Post(p) => p.community_id, 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?.into()) + Ok( + Community::read(&mut context.pool(), cid) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? + .into(), + ) } } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 74d755da0..8c533ba88 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -10,7 +10,7 @@ use activitypub_federation::{ }; use chrono::{DateTime, Utc}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use serde::Deserialize; use url::Url; @@ -21,7 +21,7 @@ use url::Url; pub(crate) async fn search_query_to_object_id( mut query: String, context: &Data, -) -> Result { +) -> LemmyResult { Ok(match Url::parse(&query) { Ok(url) => { // its already an url, just go with it @@ -46,7 +46,7 @@ pub(crate) async fn search_query_to_object_id( pub(crate) async fn search_query_to_object_id_local( query: &str, context: &Data, -) -> Result { +) -> LemmyResult { let url = Url::parse(query)?; ObjectId::from(url).dereference_local(context).await } @@ -90,7 +90,7 @@ impl Object for SearchableObjects { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { let uc = UserOrCommunity::read_from_id(object_id.clone(), context).await?; if let Some(uc) = uc { return Ok(Some(SearchableObjects::PersonOrCommunity(Box::new(uc)))); @@ -107,7 +107,7 @@ impl Object for SearchableObjects { } #[tracing::instrument(skip_all)] - async fn delete(self, data: &Data) -> Result<(), LemmyError> { + async fn delete(self, data: &Data) -> LemmyResult<()> { match self { SearchableObjects::Post(p) => p.delete(data).await, SearchableObjects::Comment(c) => c.delete(data).await, @@ -118,7 +118,7 @@ impl Object for SearchableObjects { } } - async fn into_json(self, _data: &Data) -> Result { + async fn into_json(self, _data: &Data) -> LemmyResult { unimplemented!() } @@ -127,7 +127,7 @@ impl Object for SearchableObjects { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match apub { SearchableKinds::Page(a) => ApubPost::verify(a, expected_domain, data).await, SearchableKinds::Note(a) => ApubComment::verify(a, expected_domain, data).await, @@ -139,7 +139,7 @@ impl Object for SearchableObjects { } #[tracing::instrument(skip_all)] - async fn from_json(apub: Self::Kind, context: &Data) -> Result { + async fn from_json(apub: Self::Kind, context: &Data) -> LemmyResult { use SearchableKinds as SAT; use SearchableObjects as SO; Ok(match apub { diff --git a/crates/apub/src/fetcher/site_or_community_or_user.rs b/crates/apub/src/fetcher/site_or_community_or_user.rs index 76ee566c9..30b5fd568 100644 --- a/crates/apub/src/fetcher/site_or_community_or_user.rs +++ b/crates/apub/src/fetcher/site_or_community_or_user.rs @@ -9,7 +9,7 @@ use activitypub_federation::{ }; use chrono::{DateTime, Utc}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -44,19 +44,19 @@ impl Object for SiteOrCommunityOrUser { async fn read_from_id( _object_id: Url, _data: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { unimplemented!(); } #[tracing::instrument(skip_all)] - async fn delete(self, data: &Data) -> Result<(), LemmyError> { + async fn delete(self, data: &Data) -> LemmyResult<()> { match self { SiteOrCommunityOrUser::Site(p) => p.delete(data).await, SiteOrCommunityOrUser::UserOrCommunity(p) => p.delete(data).await, } } - async fn into_json(self, _data: &Data) -> Result { + async fn into_json(self, _data: &Data) -> LemmyResult { unimplemented!() } @@ -65,7 +65,7 @@ impl Object for SiteOrCommunityOrUser { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match apub { SiteOrPersonOrGroup::Instance(a) => ApubSite::verify(a, expected_domain, data).await, SiteOrPersonOrGroup::PersonOrGroup(a) => { @@ -75,7 +75,7 @@ impl Object for SiteOrCommunityOrUser { } #[tracing::instrument(skip_all)] - async fn from_json(_apub: Self::Kind, _data: &Data) -> Result { + async fn from_json(_apub: Self::Kind, _data: &Data) -> LemmyResult { unimplemented!(); } } diff --git a/crates/apub/src/fetcher/user_or_community.rs b/crates/apub/src/fetcher/user_or_community.rs index 93e955c7b..d29cbb6b0 100644 --- a/crates/apub/src/fetcher/user_or_community.rs +++ b/crates/apub/src/fetcher/user_or_community.rs @@ -10,7 +10,7 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::activity::ActorType; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyResult}; use serde::{Deserialize, Serialize}; use url::Url; @@ -47,10 +47,7 @@ impl Object for UserOrCommunity { } #[tracing::instrument(skip_all)] - async fn read_from_id( - object_id: Url, - data: &Data, - ) -> Result, LemmyError> { + async fn read_from_id(object_id: Url, data: &Data) -> LemmyResult> { let person = ApubPerson::read_from_id(object_id.clone(), data).await?; Ok(match person { Some(o) => Some(UserOrCommunity::User(o)), @@ -61,14 +58,14 @@ impl Object for UserOrCommunity { } #[tracing::instrument(skip_all)] - async fn delete(self, data: &Data) -> Result<(), LemmyError> { + async fn delete(self, data: &Data) -> LemmyResult<()> { match self { UserOrCommunity::User(p) => p.delete(data).await, UserOrCommunity::Community(p) => p.delete(data).await, } } - async fn into_json(self, _data: &Data) -> Result { + async fn into_json(self, _data: &Data) -> LemmyResult { unimplemented!() } @@ -77,7 +74,7 @@ impl Object for UserOrCommunity { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { match apub { PersonOrGroup::Person(a) => ApubPerson::verify(a, expected_domain, data).await, PersonOrGroup::Group(a) => ApubCommunity::verify(a, expected_domain, data).await, @@ -85,7 +82,7 @@ impl Object for UserOrCommunity { } #[tracing::instrument(skip_all)] - async fn from_json(apub: Self::Kind, data: &Data) -> Result { + async fn from_json(apub: Self::Kind, data: &Data) -> LemmyResult { Ok(match apub { PersonOrGroup::Person(p) => UserOrCommunity::User(ApubPerson::from_json(p, data).await?), PersonOrGroup::Group(p) => { diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index 200dda8ed..17711817e 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::Deserialize; #[derive(Deserialize)] @@ -28,12 +28,19 @@ pub(crate) struct CommentQuery { pub(crate) async fn get_apub_comment( info: Path, context: Data, -) -> Result { +) -> 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?.into(); - let post = Post::read(&mut context.pool(), comment.post_id).await?; - let community = Community::read(&mut context.pool(), post.community_id).await?; + 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)?; check_community_public(&community)?; if !comment.local { diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index cf0d1625d..c7a1f9eda 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -18,7 +18,7 @@ use activitypub_federation::{ use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::ApubActor}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::Deserialize; #[derive(Deserialize, Clone)] @@ -31,10 +31,11 @@ pub(crate) struct CommunityQuery { pub(crate) async fn get_apub_community_http( info: web::Path, context: Data, -) -> Result { +) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, true) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); if community.deleted || community.removed { @@ -52,7 +53,7 @@ pub async fn community_inbox( request: HttpRequest, body: Bytes, data: Data, -) -> Result { +) -> LemmyResult { receive_activity::, ApubPerson, LemmyContext>( request, body, &data, ) @@ -63,9 +64,10 @@ pub async fn community_inbox( pub(crate) async fn get_apub_community_followers( info: web::Path, context: Data, -) -> Result { - let community = - Community::read_from_name(&mut context.pool(), &info.community_name, false).await?; +) -> LemmyResult { + let community = Community::read_from_name(&mut context.pool(), &info.community_name, false) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; check_community_public(&community)?; let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?; create_apub_response(&followers) @@ -76,10 +78,11 @@ pub(crate) async fn get_apub_community_followers( pub(crate) async fn get_apub_community_outbox( info: web::Path, context: Data, -) -> Result { +) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); check_community_public(&community)?; let outbox = ApubCommunityOutbox::read_local(&community, &context).await?; @@ -90,10 +93,11 @@ pub(crate) async fn get_apub_community_outbox( pub(crate) async fn get_apub_community_moderators( info: web::Path, context: Data, -) -> Result { +) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); check_community_public(&community)?; let moderators = ApubCommunityModerators::read_local(&community, &context).await?; @@ -104,10 +108,11 @@ pub(crate) async fn get_apub_community_moderators( pub(crate) async fn get_apub_community_featured( info: web::Path, context: Data, -) -> Result { +) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)? .into(); check_community_public(&community)?; let featured = ApubCommunityFeatured::read_local(&community, &context).await?; @@ -127,7 +132,6 @@ pub(crate) mod tests { traits::Crud, CommunityVisibility, }; - use lemmy_utils::error::LemmyResult; use serde::de::DeserializeOwned; use serial_test::serial; @@ -135,7 +139,7 @@ pub(crate) mod tests { deleted: bool, visibility: CommunityVisibility, context: &Data, - ) -> Result<(Instance, Community), LemmyError> { + ) -> LemmyResult<(Instance, Community)> { let instance = Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?; let community_form = CommunityInsertForm::builder() @@ -150,7 +154,7 @@ pub(crate) mod tests { Ok((instance, community)) } - async fn decode_response(res: HttpResponse) -> Result { + async fn decode_response(res: HttpResponse) -> LemmyResult { let body = to_bytes(res.into_body()).await.unwrap(); 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 b400e3dab..8aba7832f 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -18,7 +18,7 @@ use lemmy_db_schema::{ source::{activity::SentActivity, community::Community}, CommunityVisibility, }; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use std::ops::Deref; use url::Url; @@ -88,7 +88,7 @@ pub struct ActivityQuery { pub(crate) async fn get_activity( info: web::Path, context: web::Data, -) -> Result { +) -> LemmyResult { let settings = context.settings(); let activity_id = Url::parse(&format!( "{}/activities/{}/{}", @@ -97,7 +97,9 @@ pub(crate) async fn get_activity( info.id ))? .into(); - let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id).await?; + let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id) + .await? + .ok_or(LemmyErrorType::CouldntFindActivity)?; let sensitive = activity.sensitive; if sensitive { diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index 254313634..ba2372fe8 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -14,7 +14,7 @@ use activitypub_federation::{ use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url}; use lemmy_db_schema::{source::person::Person, traits::ApubActor}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::Deserialize; #[derive(Deserialize)] @@ -27,11 +27,12 @@ pub struct PersonQuery { pub(crate) async fn get_apub_person_http( info: web::Path, context: Data, -) -> Result { +) -> LemmyResult { let user_name = info.into_inner().user_name; // 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)? .into(); if !person.deleted { @@ -48,7 +49,7 @@ pub async fn person_inbox( request: HttpRequest, body: Bytes, data: Data, -) -> Result { +) -> LemmyResult { receive_activity::, UserOrCommunity, LemmyContext>( request, body, &data, ) @@ -59,8 +60,10 @@ pub async fn person_inbox( pub(crate) async fn get_apub_person_outbox( info: web::Path, context: Data, -) -> Result { - let person = Person::read_from_name(&mut context.pool(), &info.user_name, false).await?; +) -> LemmyResult { + let person = Person::read_from_name(&mut context.pool(), &info.user_name, false) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; 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 6ca07f684..513cba7ea 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::Deserialize; #[derive(Deserialize)] @@ -28,11 +28,16 @@ pub(crate) struct PostQuery { pub(crate) async fn get_apub_post( info: web::Path, context: Data, -) -> Result { +) -> 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?.into(); - let community = Community::read(&mut context.pool(), post.community_id).await?; + 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)?; check_community_public(&community)?; if !post.local { diff --git a/crates/apub/src/http/site.rs b/crates/apub/src/http/site.rs index 410b29b18..54d3c0e32 100644 --- a/crates/apub/src/http/site.rs +++ b/crates/apub/src/http/site.rs @@ -7,22 +7,22 @@ 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use url::Url; -pub(crate) async fn get_apub_site_http( - context: Data, -) -> Result { - let site: ApubSite = SiteView::read_local(&mut context.pool()).await?.site.into(); +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 apub = site.into_json(&context).await?; create_apub_response(&apub) } #[tracing::instrument(skip_all)] -pub(crate) async fn get_apub_site_outbox( - context: Data, -) -> Result { +pub(crate) async fn get_apub_site_outbox(context: Data) -> LemmyResult { let outbox_id = format!( "{}/site_outbox", context.settings().get_protocol_and_hostname() diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index c09a3007c..f500c41ee 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -11,7 +11,7 @@ use lemmy_db_schema::{ }; use lemmy_utils::{ error::{LemmyError, LemmyErrorType, LemmyResult}, - CACHE_DURATION_SHORT, + CACHE_DURATION_FEDERATION, }; use moka::future::Cache; use once_cell::sync::Lazy; @@ -77,7 +77,7 @@ impl UrlVerifier for VerifyUrlData { /// - URL being in the allowlist (if it is active) /// - URL not being in the blocklist (if it is active) #[tracing::instrument(skip(local_site_data))] -fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> Result<(), LemmyError> { +fn check_apub_id_valid(apub_id: &Url, local_site_data: &LocalSiteData) -> LemmyResult<()> { let domain = apub_id.domain().expect("apud id has domain").to_string(); if !local_site_data @@ -127,7 +127,7 @@ pub(crate) async fn local_site_data_cached( static CACHE: Lazy>> = Lazy::new(|| { Cache::builder() .max_capacity(1) - .time_to_live(CACHE_DURATION_SHORT) + .time_to_live(CACHE_DURATION_FEDERATION) .build() }); Ok( @@ -157,7 +157,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness( apub_id: &Url, is_strict: bool, context: &LemmyContext, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let domain = apub_id.domain().expect("apud id has domain").to_string(); let local_instance = context .settings() @@ -195,13 +195,10 @@ pub(crate) async fn check_apub_id_valid_with_strictness( /// Store received activities in the database. /// -/// This ensures that the same activity doesnt get received and processed more than once, which +/// This ensures that the same activity doesn't get received and processed more than once, which /// would be a waste of resources. #[tracing::instrument(skip(data))] -async fn insert_received_activity( - ap_id: &Url, - data: &Data, -) -> Result<(), LemmyError> { +async fn insert_received_activity(ap_id: &Url, data: &Data) -> LemmyResult<()> { ReceivedActivity::create(&mut data.pool(), &ap_id.clone().into()).await?; Ok(()) } diff --git a/crates/apub/src/mentions.rs b/crates/apub/src/mentions.rs index b088dfd03..4f4edc76d 100644 --- a/crates/apub/src/mentions.rs +++ b/crates/apub/src/mentions.rs @@ -11,7 +11,7 @@ use lemmy_db_schema::{ traits::Crud, utils::DbPool, }; -use lemmy_utils::{error::LemmyError, utils::mention::scrape_text_for_mentions}; +use lemmy_utils::{error::LemmyResult, utils::mention::scrape_text_for_mentions, LemmyErrorType}; use serde::{Deserialize, Serialize}; use serde_json::Value; use url::Url; @@ -44,7 +44,7 @@ pub async fn collect_non_local_mentions( comment: &ApubComment, community_id: ObjectId, context: &Data, -) -> Result { +) -> LemmyResult { let parent_creator = get_comment_parent_creator(&mut context.pool(), comment).await?; let mut addressed_ccs: Vec = vec![community_id.into(), parent_creator.id()]; @@ -94,14 +94,23 @@ pub async fn collect_non_local_mentions( async fn get_comment_parent_creator( pool: &mut DbPool<'_>, comment: &Comment, -) -> Result { +) -> 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?; + let parent_comment = Comment::read(pool, parent_comment_id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; parent_comment.creator_id } else { let parent_post_id = comment.post_id; - let parent_post = Post::read(pool, parent_post_id).await?; + let parent_post = Post::read(pool, parent_post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; parent_post.creator_id }; - Ok(Person::read(pool, parent_creator_id).await?.into()) + Ok( + Person::read(pool, parent_creator_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? + .into(), + ) } diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index ba7cc914f..466094b7f 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -29,9 +29,10 @@ use lemmy_db_schema::{ post::Post, }, traits::Crud, + utils::naive_now, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType}, + error::{LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::markdown_to_html, }; use std::ops::Deref; @@ -67,7 +68,7 @@ impl Object for ApubComment { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { Ok( Comment::read_from_apub_id(&mut context.pool(), object_id) .await? @@ -76,7 +77,7 @@ impl Object for ApubComment { } #[tracing::instrument(skip_all)] - async fn delete(self, context: &Data) -> Result<(), LemmyError> { + async fn delete(self, context: &Data) -> LemmyResult<()> { if !self.deleted { let form = CommentUpdateForm { deleted: Some(true), @@ -88,17 +89,25 @@ impl Object for ApubComment { } #[tracing::instrument(skip_all)] - async fn into_json(self, context: &Data) -> Result { + async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id).await?; + let creator = Person::read(&mut context.pool(), creator_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let post_id = self.post_id; - let post = Post::read(&mut context.pool(), post_id).await?; + let post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; let community_id = post.community_id; - let community = Community::read(&mut context.pool(), community_id).await?; + let community = Community::read(&mut context.pool(), community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; let in_reply_to = if let Some(comment_id) = self.parent_comment_id() { - let parent_comment = Comment::read(&mut context.pool(), comment_id).await?; + let parent_comment = Comment::read(&mut context.pool(), comment_id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; parent_comment.ap_id.into() } else { post.ap_id.into() @@ -132,15 +141,16 @@ impl Object for ApubComment { note: &Note, expected_domain: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { 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?; check_apub_id_valid_with_strictness(note.id.inner(), community.local, context).await?; - verify_is_remote_object(note.id.inner(), context.settings())?; + verify_is_remote_object(¬e.id, context)?; 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 is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &creator, community.id) @@ -157,7 +167,7 @@ impl Object for ApubComment { /// /// If the parent community, post and comment(s) are not known locally, these are also fetched. #[tracing::instrument(skip_all)] - async fn from_json(note: Note, context: &Data) -> Result { + async fn from_json(note: Note, context: &Data) -> LemmyResult { let creator = note.attributed_to.dereference(context).await?; let (post, parent_comment) = note.get_parents(context).await?; @@ -184,7 +194,14 @@ impl Object for ApubComment { language_id, }; let parent_comment_path = parent_comment.map(|t| t.0.path); - let comment = Comment::create(&mut context.pool(), &form, parent_comment_path.as_ref()).await?; + let timestamp: DateTime = note.updated.or(note.published).unwrap_or_else(naive_now); + let comment = Comment::insert_apub( + &mut context.pool(), + Some(timestamp), + &form, + parent_comment_path.as_ref(), + ) + .await?; Ok(comment.into()) } } @@ -204,7 +221,6 @@ pub(crate) mod tests { use assert_json_diff::assert_json_include; use html2md::parse_html; use lemmy_db_schema::source::site::Site; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; @@ -212,7 +228,7 @@ pub(crate) mod tests { url: &Url, context: &Data, ) -> LemmyResult<(ApubPerson, ApubCommunity, ApubPost, ApubSite)> { - // use separate counter so this doesnt affect tests + // use separate counter so this doesn't affect tests let context2 = context.reset_request_count(); let (person, site) = parse_lemmy_person(&context2).await?; let community = parse_lemmy_community(&context2).await?; diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 7630d80b2..1526a1a37 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -38,7 +38,11 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_db_views_actor::structs::CommunityFollowerView; -use lemmy_utils::{error::LemmyError, spawn_try_task, utils::markdown::markdown_to_html}; +use lemmy_utils::{ + error::{LemmyError, LemmyResult}, + spawn_try_task, + utils::markdown::markdown_to_html, +}; use std::ops::Deref; use url::Url; @@ -72,7 +76,7 @@ impl Object for ApubCommunity { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { Ok( Community::read_from_apub_id(&mut context.pool(), &object_id.into()) .await? @@ -81,7 +85,7 @@ impl Object for ApubCommunity { } #[tracing::instrument(skip_all)] - async fn delete(self, context: &Data) -> Result<(), LemmyError> { + async fn delete(self, context: &Data) -> LemmyResult<()> { let form = CommunityUpdateForm { deleted: Some(true), ..Default::default() @@ -91,7 +95,7 @@ impl Object for ApubCommunity { } #[tracing::instrument(skip_all)] - async fn into_json(self, data: &Data) -> Result { + async fn into_json(self, data: &Data) -> LemmyResult { let community_id = self.id; let langs = CommunityLanguage::read(&mut data.pool(), community_id).await?; let language = LanguageTag::new_multiple(langs, &mut data.pool()).await?; @@ -128,16 +132,13 @@ impl Object for ApubCommunity { group: &Group, expected_domain: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { group.verify(expected_domain, context).await } /// Converts a `Group` to `Community`, inserts it into the database and updates moderators. #[tracing::instrument(skip_all)] - async fn from_json( - group: Group, - context: &Data, - ) -> Result { + async fn from_json(group: Group, context: &Data) -> LemmyResult { let instance_id = fetch_instance_actor_for_object(&group.id, context).await?; let local_site = LocalSite::read(&mut context.pool()).await.ok(); @@ -175,7 +176,8 @@ impl Object for ApubCommunity { let languages = LanguageTag::to_language_id_multiple(group.language, &mut context.pool()).await?; - let community = Community::create(&mut context.pool(), &form).await?; + let timestamp = group.updated.or(group.published).unwrap_or_else(naive_now); + let community = Community::insert_apub(&mut context.pool(), timestamp, &form).await?; CommunityLanguage::update(&mut context.pool(), languages, community.id).await?; let community: ApubCommunity = community.into(); @@ -234,10 +236,7 @@ impl GetActorType for ApubCommunity { impl ApubCommunity { /// For a given community, returns the inboxes of all followers. #[tracing::instrument(skip_all)] - pub(crate) async fn get_follower_inboxes( - &self, - context: &LemmyContext, - ) -> Result, LemmyError> { + pub(crate) async fn get_follower_inboxes(&self, context: &LemmyContext) -> LemmyResult> { let id = self.id; let local_site_data = local_site_data_cached(&mut context.pool()).await?; @@ -264,14 +263,13 @@ pub(crate) mod tests { }; use activitypub_federation::fetch::collection_id::CollectionId; use lemmy_db_schema::source::site::Site; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; pub(crate) async fn parse_lemmy_community( context: &Data, ) -> LemmyResult { - // use separate counter so this doesnt affect tests + // use separate counter so this doesn't affect tests let context2 = context.reset_request_count(); let mut json: Group = file_to_json_object("assets/lemmy/objects/group.json")?; // change these links so they dont fetch over the network diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 6894643d6..145dc63c2 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -1,3 +1,4 @@ +use super::verify_is_remote_object; use crate::{ activities::GetActorType, check_apub_id_valid_with_strictness, @@ -39,7 +40,7 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyResult}, utils::{ markdown::markdown_to_html, slurs::{check_slurs, check_slurs_opt}, @@ -76,10 +77,7 @@ impl Object for ApubSite { } #[tracing::instrument(skip_all)] - async fn read_from_id( - object_id: Url, - data: &Data, - ) -> Result, LemmyError> { + async fn read_from_id(object_id: Url, data: &Data) -> LemmyResult> { Ok( Site::read_from_apub_id(&mut data.pool(), &object_id.into()) .await? @@ -87,12 +85,12 @@ impl Object for ApubSite { ) } - async fn delete(self, _data: &Data) -> Result<(), LemmyError> { + async fn delete(self, _data: &Data) -> LemmyResult<()> { unimplemented!() } #[tracing::instrument(skip_all)] - async fn into_json(self, data: &Data) -> Result { + async fn into_json(self, data: &Data) -> LemmyResult { let site_id = self.id; let langs = SiteLanguage::read(&mut data.pool(), site_id).await?; let language = LanguageTag::new_multiple(langs, &mut data.pool()).await?; @@ -124,9 +122,10 @@ impl Object for ApubSite { apub: &Self::Kind, expected_domain: &Url, data: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { check_apub_id_valid_with_strictness(apub.id.inner(), true, data).await?; verify_domains_match(expected_domain, apub.id.inner())?; + verify_is_remote_object(&apub.id, data)?; let local_site_data = local_site_data_cached(&mut data.pool()).await?; let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site); @@ -137,7 +136,7 @@ impl Object for ApubSite { } #[tracing::instrument(skip_all)] - async fn from_json(apub: Self::Kind, context: &Data) -> Result { + async fn from_json(apub: Self::Kind, context: &Data) -> LemmyResult { let domain = apub.id.inner().domain().expect("group id has domain"); let instance = DbInstance::read_or_create(&mut context.pool(), domain.to_string()).await?; @@ -200,7 +199,7 @@ impl GetActorType for ApubSite { pub(in crate::objects) async fn fetch_instance_actor_for_object + Clone>( object_id: &T, context: &Data, -) -> Result { +) -> LemmyResult { let object_id: Url = object_id.clone().into(); let instance_id = Site::instance_actor_id_from_url(object_id); let site = ObjectId::::from(instance_id.clone()) @@ -225,7 +224,6 @@ pub(in crate::objects) async fn fetch_instance_actor_for_object + C pub(crate) mod tests { use super::*; use crate::protocol::tests::file_to_json_object; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index cabd07e6d..e199ebfad 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,9 +1,16 @@ use crate::protocol::Source; -use activitypub_federation::protocol::values::MediaTypeMarkdownOrHtml; +use activitypub_federation::{ + config::Data, + fetch::object_id::ObjectId, + protocol::values::MediaTypeMarkdownOrHtml, + traits::Object, +}; use anyhow::anyhow; use html2md::parse_html; -use lemmy_utils::{error::LemmyError, settings::structs::Settings}; -use url::Url; +use lemmy_api_common::context::LemmyContext; +use lemmy_utils::error::LemmyResult; +use serde::Deserialize; +use std::fmt::Debug; pub mod comment; pub mod community; @@ -43,9 +50,15 @@ pub(crate) fn read_from_string_or_source_opt( /// wrapped in Announce. If we simply receive this like any other federated object, overwrite the /// existing, local Post. In particular, it will set the field local = false, so that the object /// can't be fetched from the Activitypub HTTP endpoint anymore (which only serves local objects). -pub(crate) fn verify_is_remote_object(id: &Url, settings: &Settings) -> Result<(), LemmyError> { - let local_domain = settings.get_hostname_without_port()?; - if id.domain() == Some(&local_domain) { +pub(crate) fn verify_is_remote_object( + id: &ObjectId, + context: &Data, +) -> LemmyResult<()> +where + T: Object + Debug + Send + 'static, + for<'de2> ::Kind: Deserialize<'de2>, +{ + if id.is_local(context) { Err(anyhow!("cant accept local object from remote instance").into()) } else { Ok(()) diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index d4456344f..1aac170d7 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -1,3 +1,4 @@ +use super::verify_is_remote_object; use crate::{ activities::GetActorType, check_apub_id_valid_with_strictness, @@ -38,7 +39,7 @@ use lemmy_db_schema::{ utils::naive_now, }; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyResult}, utils::{ markdown::markdown_to_html, slurs::{check_slurs, check_slurs_opt}, @@ -77,7 +78,7 @@ impl Object for ApubPerson { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { Ok( DbPerson::read_from_apub_id(&mut context.pool(), &object_id.into()) .await? @@ -86,7 +87,7 @@ impl Object for ApubPerson { } #[tracing::instrument(skip_all)] - async fn delete(self, context: &Data) -> Result<(), LemmyError> { + async fn delete(self, context: &Data) -> LemmyResult<()> { let form = PersonUpdateForm { deleted: Some(true), ..Default::default() @@ -96,7 +97,7 @@ impl Object for ApubPerson { } #[tracing::instrument(skip_all)] - async fn into_json(self, _context: &Data) -> Result { + async fn into_json(self, _context: &Data) -> LemmyResult { let kind = if self.bot_account { UserTypes::Service } else { @@ -130,13 +131,14 @@ impl Object for ApubPerson { person: &Person, expected_domain: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { let local_site_data = local_site_data_cached(&mut context.pool()).await?; let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site); check_slurs(&person.preferred_username, slur_regex)?; check_slurs_opt(&person.name, slur_regex)?; verify_domains_match(person.id.inner(), expected_domain)?; + verify_is_remote_object(&person.id, context)?; check_apub_id_valid_with_strictness(person.id.inner(), false, context).await?; let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source); @@ -145,10 +147,7 @@ impl Object for ApubPerson { } #[tracing::instrument(skip_all)] - async fn from_json( - person: Person, - context: &Data, - ) -> Result { + async fn from_json(person: Person, context: &Data) -> LemmyResult { let instance_id = fetch_instance_actor_for_object(&person.id, context).await?; let local_site = LocalSite::read(&mut context.pool()).await.ok(); @@ -228,7 +227,6 @@ pub(crate) mod tests { }; use activitypub_federation::fetch::object_id::ObjectId; use lemmy_db_schema::source::site::Site; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 0ddc6d17b..ff11c985c 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -41,10 +41,11 @@ use lemmy_db_schema::{ post::{Post, PostInsertForm, PostUpdateForm}, }, traits::Crud, + utils::naive_now, }; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ - error::LemmyError, + error::{LemmyError, LemmyErrorType, LemmyResult}, utils::{markdown::markdown_to_html, slurs::check_slurs_opt, validation::check_url_scheme}, }; use std::ops::Deref; @@ -83,7 +84,7 @@ impl Object for ApubPost { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { Ok( Post::read_from_apub_id(&mut context.pool(), object_id) .await? @@ -92,7 +93,7 @@ impl Object for ApubPost { } #[tracing::instrument(skip_all)] - async fn delete(self, context: &Data) -> Result<(), LemmyError> { + async fn delete(self, context: &Data) -> LemmyResult<()> { if !self.deleted { let form = PostUpdateForm { deleted: Some(true), @@ -105,11 +106,15 @@ impl Object for ApubPost { // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. #[tracing::instrument(skip_all)] - async fn into_json(self, context: &Data) -> Result { + async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id).await?; + let creator = Person::read(&mut context.pool(), creator_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let community_id = self.community_id; - let community = Community::read(&mut context.pool(), community_id).await?; + 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 attachment = self @@ -159,12 +164,12 @@ impl Object for ApubPost { page: &Page, expected_domain: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { // We can't verify the domain in case of mod action, because the mod may be on a different // instance from the post author. if !page.is_mod_action(context).await? { verify_domains_match(page.id.inner(), expected_domain)?; - verify_is_remote_object(page.id.inner(), context.settings())?; + verify_is_remote_object(&page.id, context)?; }; let community = page.community(context).await?; @@ -181,7 +186,7 @@ impl Object for ApubPost { } #[tracing::instrument(skip_all)] - async fn from_json(page: Page, context: &Data) -> Result { + async fn from_json(page: Page, context: &Data) -> LemmyResult { let creator = page.creator()?.dereference(context).await?; let community = page.community(context).await?; if community.posting_restricted_to_mods { @@ -270,10 +275,12 @@ impl Object for ApubPost { .build() }; - let post = Post::create(&mut context.pool(), &form).await?; + let timestamp = page.updated.or(page.published).unwrap_or_else(naive_now); + let post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?; generate_post_link_metadata( post.clone(), + None, page.image.map(|i| i.url), |_| None, local_site, @@ -305,7 +312,6 @@ mod tests { protocol::tests::file_to_json_object, }; use lemmy_db_schema::source::site::Site; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 647510802..35f2fe418 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -1,3 +1,4 @@ +use super::verify_is_remote_object; use crate::{ check_apub_id_valid_with_strictness, objects::read_from_string_or_source, @@ -23,9 +24,10 @@ use lemmy_db_schema::{ private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, + utils::naive_now, }; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType}, + error::{LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::markdown_to_html, }; use std::ops::Deref; @@ -61,7 +63,7 @@ impl Object for ApubPrivateMessage { async fn read_from_id( object_id: Url, context: &Data, - ) -> Result, LemmyError> { + ) -> LemmyResult> { Ok( PrivateMessage::read_from_apub_id(&mut context.pool(), object_id) .await? @@ -69,18 +71,22 @@ impl Object for ApubPrivateMessage { ) } - async fn delete(self, _context: &Data) -> Result<(), LemmyError> { + async fn delete(self, _context: &Data) -> LemmyResult<()> { // do nothing, because pm can't be fetched over http unimplemented!() } #[tracing::instrument(skip_all)] - async fn into_json(self, context: &Data) -> Result { + async fn into_json(self, context: &Data) -> LemmyResult { let creator_id = self.creator_id; - let creator = Person::read(&mut context.pool(), creator_id).await?; + let creator = Person::read(&mut context.pool(), creator_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let recipient_id = self.recipient_id; - let recipient = Person::read(&mut context.pool(), recipient_id).await?; + let recipient = Person::read(&mut context.pool(), recipient_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; let note = ChatMessage { r#type: ChatMessageType::ChatMessage, @@ -101,9 +107,10 @@ impl Object for ApubPrivateMessage { note: &ChatMessage, expected_domain: &Url, context: &Data, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { verify_domains_match(note.id.inner(), expected_domain)?; verify_domains_match(note.attributed_to.inner(), note.id.inner())?; + verify_is_remote_object(¬e.id, context)?; check_apub_id_valid_with_strictness(note.id.inner(), false, context).await?; let person = note.attributed_to.dereference(context).await?; @@ -120,7 +127,7 @@ impl Object for ApubPrivateMessage { async fn from_json( note: ChatMessage, context: &Data, - ) -> Result { + ) -> 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?; @@ -142,7 +149,8 @@ impl Object for ApubPrivateMessage { ap_id: Some(note.id.into()), local: Some(false), }; - let pm = PrivateMessage::create(&mut context.pool(), &form).await?; + let timestamp = note.updated.or(note.published).unwrap_or_else(naive_now); + let pm = PrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?; Ok(pm.into()) } } @@ -159,7 +167,6 @@ mod tests { }; use assert_json_diff::assert_json_include; use lemmy_db_schema::source::site::Site; - use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/apub/src/protocol/activities/block/block_user.rs b/crates/apub/src/protocol/activities/block/block_user.rs index c5cb4306c..c1a4c64c7 100644 --- a/crates/apub/src/protocol/activities/block/block_user.rs +++ b/crates/apub/src/protocol/activities/block/block_user.rs @@ -12,7 +12,7 @@ use activitypub_federation::{ use anyhow::anyhow; use chrono::{DateTime, Utc}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -45,7 +45,7 @@ pub struct BlockUser { #[async_trait::async_trait] impl InCommunity for BlockUser { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let target = self.target.dereference(context).await?; let community = match target { SiteOrCommunity::Community(c) => c, 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 758d3fd4b..491ec7ed1 100644 --- a/crates/apub/src/protocol/activities/block/undo_block_user.rs +++ b/crates/apub/src/protocol/activities/block/undo_block_user.rs @@ -10,7 +10,7 @@ use activitypub_federation::{ protocol::helpers::deserialize_one_or_many, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -33,7 +33,7 @@ pub struct UndoBlockUser { #[async_trait::async_trait] impl InCommunity for UndoBlockUser { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.object.community(context).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; diff --git a/crates/apub/src/protocol/activities/community/announce.rs b/crates/apub/src/protocol/activities/community/announce.rs index e149e5fd1..60720365a 100644 --- a/crates/apub/src/protocol/activities/community/announce.rs +++ b/crates/apub/src/protocol/activities/community/announce.rs @@ -23,7 +23,7 @@ pub struct AnnounceActivity { } /// Use this to receive community inbox activities, and then announce them if valid. This -/// ensures that all json fields are kept, even if Lemmy doesnt understand them. +/// ensures that all json fields are kept, even if Lemmy doesn't understand them. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RawAnnouncableActivities { pub(crate) id: Url, diff --git a/crates/apub/src/protocol/activities/community/collection_add.rs b/crates/apub/src/protocol/activities/community/collection_add.rs index edf67740a..0e2ab75a6 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use url::Url; @@ -33,9 +33,11 @@ pub struct CollectionAdd { #[async_trait::async_trait] impl InCommunity for CollectionAdd { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let (community, _) = - Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; + Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; 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 960951732..51c4761ba 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use url::Url; @@ -33,9 +33,11 @@ pub struct CollectionRemove { #[async_trait::async_trait] impl InCommunity for CollectionRemove { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let (community, _) = - Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()).await?; + Community::get_by_collection_url(&mut context.pool(), &self.clone().target.into()) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; 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 33b108531..c60b86cf8 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use strum_macros::Display; use url::Url; @@ -53,9 +53,11 @@ pub struct UndoLockPage { #[async_trait::async_trait] impl InCommunity for LockPage { - async fn community(&self, context: &Data) -> Result { + 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?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } @@ -65,7 +67,7 @@ impl InCommunity for LockPage { #[async_trait::async_trait] impl InCommunity for UndoLockPage { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.object.community(context).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 adcddfdfc..7698cde50 100644 --- a/crates/apub/src/protocol/activities/community/report.rs +++ b/crates/apub/src/protocol/activities/community/report.rs @@ -11,7 +11,7 @@ use activitypub_federation::{ protocol::helpers::deserialize_one, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use url::Url; @@ -71,7 +71,7 @@ impl ReportObject { #[async_trait::async_trait] impl InCommunity for Report { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.to[0].dereference(context).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; diff --git a/crates/apub/src/protocol/activities/community/update.rs b/crates/apub/src/protocol/activities/community/update.rs index 49ec1f5d6..268f05073 100644 --- a/crates/apub/src/protocol/activities/community/update.rs +++ b/crates/apub/src/protocol/activities/community/update.rs @@ -10,7 +10,7 @@ use activitypub_federation::{ protocol::helpers::deserialize_one_or_many, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -34,7 +34,7 @@ pub struct UpdateCommunity { #[async_trait::async_trait] impl InCommunity for UpdateCommunity { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community: ApubCommunity = self.object.id.clone().dereference(context).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; 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 6fabc0aaa..43ffeb291 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use url::Url; @@ -34,9 +34,11 @@ pub struct CreateOrUpdateNote { #[async_trait::async_trait] impl InCommunity for CreateOrUpdateNote { - async fn community(&self, context: &Data) -> Result { + 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?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/create_or_update/page.rs b/crates/apub/src/protocol/activities/create_or_update/page.rs index ec64c31a0..21052a9ef 100644 --- a/crates/apub/src/protocol/activities/create_or_update/page.rs +++ b/crates/apub/src/protocol/activities/create_or_update/page.rs @@ -9,7 +9,7 @@ use activitypub_federation::{ protocol::helpers::deserialize_one_or_many, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -30,7 +30,7 @@ pub struct CreateOrUpdatePage { #[async_trait::async_trait] impl InCommunity for CreateOrUpdatePage { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.object.community(context).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 aefcf3f5f..3b9aad079 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -47,11 +47,13 @@ pub struct Delete { #[async_trait::async_trait] impl InCommunity for Delete { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { 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?; + let post = Post::read(&mut context.pool(), c.post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; post.community_id } DeletableObjects::Post(p) => p.community_id, @@ -60,7 +62,9 @@ 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?; + let community = Community::read(&mut context.pool(), community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; } diff --git a/crates/apub/src/protocol/activities/deletion/undo_delete.rs b/crates/apub/src/protocol/activities/deletion/undo_delete.rs index 35d9951b1..508b90113 100644 --- a/crates/apub/src/protocol/activities/deletion/undo_delete.rs +++ b/crates/apub/src/protocol/activities/deletion/undo_delete.rs @@ -10,7 +10,7 @@ use activitypub_federation::{ protocol::helpers::deserialize_one_or_many, }; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -35,7 +35,7 @@ pub struct UndoDelete { #[async_trait::async_trait] impl InCommunity for UndoDelete { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.object.community(context).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; diff --git a/crates/apub/src/protocol/activities/voting/undo_vote.rs b/crates/apub/src/protocol/activities/voting/undo_vote.rs index 746ae68df..e9ccbd593 100644 --- a/crates/apub/src/protocol/activities/voting/undo_vote.rs +++ b/crates/apub/src/protocol/activities/voting/undo_vote.rs @@ -5,7 +5,7 @@ use crate::{ }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId, kinds::activity::UndoType}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -22,7 +22,7 @@ pub struct UndoVote { #[async_trait::async_trait] impl InCommunity for UndoVote { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self.object.community(context).await?; if let Some(audience) = &self.audience { verify_community_matches(audience, community.actor_id.clone())?; diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs index 013c15bfd..b632333c7 100644 --- a/crates/apub/src/protocol/activities/voting/vote.rs +++ b/crates/apub/src/protocol/activities/voting/vote.rs @@ -6,7 +6,7 @@ use crate::{ }; use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use strum_macros::Display; use url::Url; @@ -51,7 +51,7 @@ impl From<&VoteType> for i16 { #[async_trait::async_trait] impl InCommunity for Vote { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = self .object .dereference(context) diff --git a/crates/apub/src/protocol/collections/empty_outbox.rs b/crates/apub/src/protocol/collections/empty_outbox.rs index 3801c04e9..1e7a4c6a8 100644 --- a/crates/apub/src/protocol/collections/empty_outbox.rs +++ b/crates/apub/src/protocol/collections/empty_outbox.rs @@ -1,5 +1,5 @@ use activitypub_federation::kinds::collection::OrderedCollectionType; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -14,7 +14,7 @@ pub(crate) struct EmptyOutbox { } impl EmptyOutbox { - pub(crate) fn new(outbox_id: Url) -> Result { + pub(crate) fn new(outbox_id: Url) -> LemmyResult { Ok(EmptyOutbox { r#type: OrderedCollectionType::OrderedCollection, id: outbox_id, diff --git a/crates/apub/src/protocol/mod.rs b/crates/apub/src/protocol/mod.rs index d538ee531..a4774ac1d 100644 --- a/crates/apub/src/protocol/mod.rs +++ b/crates/apub/src/protocol/mod.rs @@ -7,7 +7,7 @@ use activitypub_federation::{ }; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::newtypes::DbUrl; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; use url::Url; @@ -71,7 +71,7 @@ impl IdOrNestedObject { IdOrNestedObject::NestedObject(n) => n.object_id(), } } - pub(crate) async fn object(self, context: &Data) -> Result { + pub(crate) async fn object(self, context: &Data) -> LemmyResult { match self { // TODO: move IdOrNestedObject struct to library and make fetch_object_http private IdOrNestedObject::Id(i) => Ok(fetch_object_http(&i, context).await?.object), @@ -83,25 +83,25 @@ impl IdOrNestedObject { #[async_trait::async_trait] pub trait InCommunity { // TODO: after we use audience field and remove backwards compat, it should be possible to change - // this to simply `fn community(&self) -> Result, LemmyError>` - async fn community(&self, context: &Data) -> Result; + // this to simply `fn community(&self) -> LemmyResult>` + async fn community(&self, context: &Data) -> LemmyResult; } #[cfg(test)] pub(crate) mod tests { use activitypub_federation::protocol::context::WithContext; use assert_json_diff::assert_json_include; - use lemmy_utils::error::LemmyError; + use lemmy_utils::error::LemmyResult; use serde::{de::DeserializeOwned, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader}; - pub(crate) fn file_to_json_object(path: &str) -> Result { + pub(crate) fn file_to_json_object(path: &str) -> LemmyResult { let file = File::open(path)?; let reader = BufReader::new(file); Ok(serde_json::from_reader(reader)?) } - pub(crate) fn test_json(path: &str) -> Result, LemmyError> { + pub(crate) fn test_json(path: &str) -> LemmyResult> { file_to_json_object::>(path) } @@ -109,7 +109,7 @@ pub(crate) mod tests { /// Ensures that there are no breaking changes in sent data. pub(crate) fn test_parse_lemmy_item( path: &str, - ) -> Result { + ) -> LemmyResult { // parse file as T let parsed = file_to_json_object::(path)?; diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index 2a625a5a7..2ed884484 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -7,7 +7,7 @@ use crate::{ community_outbox::ApubCommunityOutbox, }, local_site_data_cached, - objects::{community::ApubCommunity, read_from_string_or_source_opt}, + objects::{community::ApubCommunity, read_from_string_or_source_opt, verify_is_remote_object}, protocol::{ objects::{Endpoints, LanguageTag}, ImageObject, @@ -15,6 +15,7 @@ use crate::{ }, }; use activitypub_federation::{ + config::Data, fetch::{collection_id::CollectionId, object_id::ObjectId}, kinds::actor::GroupType, protocol::{ @@ -26,7 +27,7 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex}; use lemmy_utils::{ - error::LemmyError, + error::LemmyResult, utils::slurs::{check_slurs, check_slurs_opt}, }; use serde::{Deserialize, Serialize}; @@ -75,10 +76,11 @@ impl Group { pub(crate) async fn verify( &self, expected_domain: &Url, - context: &LemmyContext, - ) -> Result<(), LemmyError> { + context: &Data, + ) -> LemmyResult<()> { check_apub_id_valid_with_strictness(self.id.inner(), true, context).await?; verify_domains_match(expected_domain, self.id.inner())?; + verify_is_remote_object(&self.id, context)?; let local_site_data = local_site_data_cached(&mut context.pool()).await?; let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site); diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index 03229f739..efda3d7ed 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -4,7 +4,7 @@ use lemmy_db_schema::{ source::language::Language, utils::DbPool, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use serde::{Deserialize, Serialize}; use url::Url; @@ -34,7 +34,7 @@ impl LanguageTag { pub(crate) async fn new_single( lang: LanguageId, pool: &mut DbPool<'_>, - ) -> Result, LemmyError> { + ) -> LemmyResult> { let lang = Language::read_from_id(pool, lang).await?; // undetermined @@ -51,7 +51,7 @@ impl LanguageTag { pub(crate) async fn new_multiple( lang_ids: Vec, pool: &mut DbPool<'_>, - ) -> Result, LemmyError> { + ) -> LemmyResult> { let mut langs = Vec::::new(); for l in lang_ids { @@ -71,7 +71,7 @@ impl LanguageTag { pub(crate) async fn to_language_id_single( lang: Option, pool: &mut DbPool<'_>, - ) -> Result, LemmyError> { + ) -> LemmyResult> { let identifier = lang.map(|l| l.identifier); let language = Language::read_id_from_code(pool, identifier.as_deref()).await?; @@ -81,7 +81,7 @@ impl LanguageTag { pub(crate) async fn to_language_id_multiple( langs: Vec, pool: &mut DbPool<'_>, - ) -> Result, LemmyError> { + ) -> LemmyResult> { let mut language_ids = Vec::new(); for l in langs { diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index 259a8fcfa..b0ae00037 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -20,7 +20,7 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::error::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use std::ops::Deref; @@ -57,14 +57,16 @@ impl Note { pub(crate) async fn get_parents( &self, context: &Data, - ) -> Result<(ApubPost, Option), LemmyError> { + ) -> 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() { 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?; + let post = Post::read(&mut context.pool(), post_id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; Ok((post.into(), Some(c.clone()))) } } @@ -73,9 +75,11 @@ impl Note { #[async_trait::async_trait] impl InCommunity for Note { - async fn community(&self, context: &Data) -> Result { + 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?; + let community = Community::read(&mut context.pool(), post.community_id) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; 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 170778297..acfd8f5fd 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -20,7 +20,7 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use itertools::Itertools; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -160,10 +160,7 @@ impl Page { /// it is a mod action and needs to be verified as such. /// /// Locked needs to be false on a newly created post (verified in [[CreatePost]]. - pub(crate) async fn is_mod_action( - &self, - context: &Data, - ) -> Result { + pub(crate) async fn is_mod_action(&self, context: &Data) -> LemmyResult { let old_post = self.id.clone().dereference_local(context).await; Ok(Page::is_locked_changed(&old_post, &self.comments_enabled)) } @@ -181,7 +178,7 @@ impl Page { false } - pub(crate) fn creator(&self) -> Result, LemmyError> { + pub(crate) fn creator(&self) -> LemmyResult> { match &self.attributed_to { AttributedTo::Lemmy(l) => Ok(l.clone()), AttributedTo::Peertube(p) => p @@ -224,10 +221,10 @@ impl ActivityHandler for Page { fn actor(&self) -> &Url { unimplemented!() } - async fn verify(&self, data: &Data) -> Result<(), LemmyError> { + async fn verify(&self, data: &Data) -> LemmyResult<()> { ApubPost::verify(self, self.id.inner(), data).await } - async fn receive(self, data: &Data) -> Result<(), LemmyError> { + async fn receive(self, data: &Data) -> LemmyResult<()> { ApubPost::from_json(self, data).await?; Ok(()) } @@ -235,7 +232,7 @@ impl ActivityHandler for Page { #[async_trait::async_trait] impl InCommunity for Page { - async fn community(&self, context: &Data) -> Result { + async fn community(&self, context: &Data) -> LemmyResult { let community = match &self.attributed_to { AttributedTo::Lemmy(_) => { let mut iter = self.to.iter().merge(self.cc.iter()); diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql new file mode 100644 index 000000000..d869a5e1e --- /dev/null +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -0,0 +1,476 @@ +-- A trigger is associated with a table instead of a schema, so they can't be in the `r` schema. This is +-- okay if the function specified after `EXECUTE FUNCTION` is in `r`, since dropping the function drops the trigger. +-- +-- Tables that are updated by triggers should not have foreign keys that aren't set to `INITIALLY DEFERRED` +-- (even if only other columns are updated) because triggers can run after the deletion of referenced rows and +-- before the automatic deletion of the row that references it. This is not a problem for insert or delete. +-- +-- +-- +-- Create triggers for both post and comments +CREATE FUNCTION r.creator_id_from_post_aggregates (agg post_aggregates) + RETURNS int IMMUTABLE PARALLEL SAFE RETURN agg.creator_id; + +CREATE FUNCTION r.creator_id_from_comment_aggregates (agg comment_aggregates) + RETURNS int IMMUTABLE PARALLEL SAFE RETURN ( + SELECT + creator_id + FROM + comment + WHERE + comment.id = agg.comment_id LIMIT 1 +); + +CREATE PROCEDURE r.post_or_comment (table_name text) +LANGUAGE plpgsql +AS $a$ +BEGIN + EXECUTE replace($b$ + -- When a thing gets a vote, update its aggregates and its creator's aggregates + CALL r.create_triggers ('thing_like', $$ + BEGIN + WITH thing_diff AS ( UPDATE + thing_aggregates AS a + SET + score = a.score + diff.upvotes - diff.downvotes, upvotes = a.upvotes + diff.upvotes, downvotes = a.downvotes + diff.downvotes, controversy_rank = r.controversy_rank ((a.upvotes + diff.upvotes)::numeric, (a.downvotes + diff.downvotes)::numeric) + FROM ( + SELECT + (thing_like).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (thing_like).thing_id) AS diff + WHERE + a.thing_id = diff.thing_id + RETURNING + r.creator_id_from_thing_aggregates (a.*) AS creator_id, diff.upvotes - diff.downvotes AS score) + UPDATE + person_aggregates AS a + SET + thing_score = a.thing_score + diff.score FROM ( + SELECT + creator_id, sum(score) AS score FROM thing_diff GROUP BY creator_id) AS diff + WHERE + a.person_id = diff.creator_id; + RETURN NULL; + END; + $$); + $b$, + 'thing', + table_name); +END; +$a$; + +CALL r.post_or_comment ('post'); + +CALL r.post_or_comment ('comment'); + +-- Create triggers that update counts in parent aggregates +CALL r.create_triggers ('comment', $$ +BEGIN + UPDATE + person_aggregates AS a + SET + comment_count = a.comment_count + diff.comment_count + FROM ( + SELECT + (comment).creator_id, coalesce(sum(count_diff), 0) AS comment_count + FROM select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (comment) + GROUP BY (comment).creator_id) AS diff +WHERE + a.person_id = diff.creator_id; + +UPDATE + site_aggregates AS a +SET + comments = a.comments + diff.comments +FROM ( + SELECT + coalesce(sum(count_diff), 0) AS comments + FROM + select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (comment) + AND (comment).local) AS diff; + +WITH post_diff AS ( + UPDATE + post_aggregates AS a + SET + comments = a.comments + diff.comments, + newest_comment_time = GREATEST (a.newest_comment_time, ( + SELECT + published + FROM select_new_rows AS new_comment + WHERE + a.post_id = new_comment.post_id ORDER BY published DESC LIMIT 1)), + newest_comment_time_necro = GREATEST (a.newest_comment_time_necro, ( + SELECT + published + FROM select_new_rows AS new_comment + WHERE + a.post_id = new_comment.post_id + -- Ignore comments from the post's creator + AND a.creator_id != new_comment.creator_id + -- Ignore comments on old posts + AND a.published > (new_comment.published - '2 days'::interval) + ORDER BY published DESC LIMIT 1)) + FROM ( + SELECT + (comment).post_id, + coalesce(sum(count_diff), 0) AS comments + FROM + select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (comment) + GROUP BY + (comment).post_id) AS diff + LEFT JOIN post ON post.id = diff.post_id + WHERE + a.post_id = diff.post_id + RETURNING + a.community_id, + diff.comments, + r.is_counted (post.*) AS include_in_community_aggregates) +UPDATE + community_aggregates AS a +SET + comments = a.comments + diff.comments +FROM ( + SELECT + community_id, + sum(comments) AS comments + FROM + post_diff + WHERE + post_diff.include_in_community_aggregates + GROUP BY + community_id) AS diff +WHERE + a.community_id = diff.community_id; + +RETURN NULL; + +END; + +$$); + +CALL r.create_triggers ('post', $$ +BEGIN + UPDATE + person_aggregates AS a + SET + post_count = a.post_count + diff.post_count + FROM ( + SELECT + (post).creator_id, coalesce(sum(count_diff), 0) AS post_count + FROM select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (post) + GROUP BY (post).creator_id) AS diff +WHERE + a.person_id = diff.creator_id; + +UPDATE + site_aggregates AS a +SET + posts = a.posts + diff.posts +FROM ( + SELECT + coalesce(sum(count_diff), 0) AS posts + FROM + select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (post) + AND (post).local) AS diff; + +UPDATE + community_aggregates AS a +SET + posts = a.posts + diff.posts +FROM ( + SELECT + (post).community_id, + coalesce(sum(count_diff), 0) AS posts + FROM + select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (post) + GROUP BY + (post).community_id) AS diff +WHERE + a.community_id = diff.community_id; + +RETURN NULL; + +END; + +$$); + +CALL r.create_triggers ('community', $$ +BEGIN + UPDATE + site_aggregates AS a + SET + communities = a.communities + diff.communities + FROM ( + SELECT + coalesce(sum(count_diff), 0) AS communities + FROM select_old_and_new_rows AS old_and_new_rows + WHERE + r.is_counted (community) + AND (community).local) AS diff; + +RETURN NULL; + +END; + +$$); + +CALL r.create_triggers ('person', $$ +BEGIN + UPDATE + site_aggregates AS a + SET + users = a.users + diff.users + FROM ( + SELECT + coalesce(sum(count_diff), 0) AS users + FROM select_old_and_new_rows AS old_and_new_rows + WHERE (person).local) AS diff; + +RETURN NULL; + +END; + +$$); + +-- For community_aggregates.comments, don't include comments of deleted or removed posts +CREATE FUNCTION r.update_comment_count_from_post () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + community_aggregates AS a + SET + comments = a.comments + diff.comments + FROM ( + SELECT + old_post.community_id, + sum(( + CASE WHEN r.is_counted (new_post.*) THEN + 1 + ELSE + -1 + END) * post_aggregates.comments) AS comments + FROM + new_post + INNER JOIN old_post ON new_post.id = old_post.id + AND (r.is_counted (new_post.*) != r.is_counted (old_post.*)) + INNER JOIN post_aggregates ON post_aggregates.post_id = new_post.id + GROUP BY + old_post.community_id) AS diff +WHERE + a.community_id = diff.community_id; + RETURN NULL; +END; +$$; + +CREATE TRIGGER comment_count + AFTER UPDATE ON post REFERENCING OLD TABLE AS old_post NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION r.update_comment_count_from_post (); + +-- Count subscribers for communities. +-- subscribers should be updated only when a local community is followed by a local or remote person. +-- subscribers_local should be updated only when a local person follows a local or remote community. +CALL r.create_triggers ('community_follower', $$ +BEGIN + UPDATE + community_aggregates AS a + SET + subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local + FROM ( + SELECT + (community_follower).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local + FROM select_old_and_new_rows AS old_and_new_rows + LEFT JOIN community ON community.id = (community_follower).community_id + LEFT JOIN person ON person.id = (community_follower).person_id GROUP BY (community_follower).community_id) AS diff +WHERE + a.community_id = diff.community_id; + +RETURN NULL; + +END; + +$$); + +-- These triggers create and update rows in each aggregates table to match its associated table's rows. +-- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints. +CREATE FUNCTION r.comment_aggregates_from_comment () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO comment_aggregates (comment_id, published) + SELECT + id, + published + FROM + new_comment; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates + AFTER INSERT ON comment REFERENCING NEW TABLE AS new_comment + FOR EACH STATEMENT + EXECUTE FUNCTION r.comment_aggregates_from_comment (); + +CREATE FUNCTION r.community_aggregates_from_community () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO community_aggregates (community_id, published) + SELECT + id, + published + FROM + new_community; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates + AFTER INSERT ON community REFERENCING NEW TABLE AS new_community + FOR EACH STATEMENT + EXECUTE FUNCTION r.community_aggregates_from_community (); + +CREATE FUNCTION r.person_aggregates_from_person () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO person_aggregates (person_id) + SELECT + id + FROM + new_person; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates + AFTER INSERT ON person REFERENCING NEW TABLE AS new_person + FOR EACH STATEMENT + EXECUTE FUNCTION r.person_aggregates_from_person (); + +CREATE FUNCTION r.post_aggregates_from_post () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO post_aggregates (post_id, published, newest_comment_time, newest_comment_time_necro, community_id, creator_id, instance_id, featured_community, featured_local) + SELECT + new_post.id, + new_post.published, + new_post.published, + new_post.published, + new_post.community_id, + new_post.creator_id, + community.instance_id, + new_post.featured_community, + new_post.featured_local + FROM + new_post + INNER JOIN community ON community.id = new_post.community_id; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates + AFTER INSERT ON post REFERENCING NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION r.post_aggregates_from_post (); + +CREATE FUNCTION r.post_aggregates_from_post_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + post_aggregates + SET + featured_community = new_post.featured_community, + featured_local = new_post.featured_local + FROM + new_post + INNER JOIN old_post ON old_post.id = new_post.id + AND (old_post.featured_community, + old_post.featured_local) != (new_post.featured_community, + old_post.featured_local) + WHERE + post_aggregates.post_id = new_post.id; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates_update + AFTER UPDATE ON post REFERENCING OLD TABLE AS old_post NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION r.post_aggregates_from_post_update (); + +CREATE FUNCTION r.site_aggregates_from_site () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + -- only 1 row can be in site_aggregates because of the index idx_site_aggregates_1_row_only. + -- we only ever want to have a single value in site_aggregate because the site_aggregate triggers update all rows in that table. + -- a cleaner check would be to insert it for the local_site but that would break assumptions at least in the tests + INSERT INTO site_aggregates (site_id) + VALUES (NEW.id) + ON CONFLICT ((TRUE)) + DO NOTHING; + RETURN NULL; +END; +$$; + +CREATE TRIGGER aggregates + AFTER INSERT ON site + FOR EACH ROW + EXECUTE FUNCTION r.site_aggregates_from_site (); + +-- Change the order of some cascading deletions to make deletion triggers run before the deletion of rows that the triggers need to read +CREATE FUNCTION r.delete_comments_before_post () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + DELETE FROM comment AS c + WHERE c.post_id = OLD.id; + RETURN OLD; +END; +$$; + +CREATE TRIGGER delete_comments + BEFORE DELETE ON post + FOR EACH ROW + EXECUTE FUNCTION r.delete_comments_before_post (); + +CREATE FUNCTION r.delete_follow_before_person () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + DELETE FROM community_follower AS c + WHERE c.person_id = OLD.id; + RETURN OLD; +END; +$$; + +CREATE TRIGGER delete_follow + BEFORE DELETE ON person + FOR EACH ROW + EXECUTE FUNCTION r.delete_follow_before_person (); + diff --git a/crates/db_schema/replaceable_schema/utils.sql b/crates/db_schema/replaceable_schema/utils.sql new file mode 100644 index 000000000..f236c5387 --- /dev/null +++ b/crates/db_schema/replaceable_schema/utils.sql @@ -0,0 +1,146 @@ +-- Each calculation used in triggers should be a single SQL language +-- expression so it can be inlined in migrations. +CREATE FUNCTION r.controversy_rank (upvotes numeric, downvotes numeric) + RETURNS float + LANGUAGE sql + IMMUTABLE PARALLEL SAFE RETURN CASE WHEN downvotes <= 0 + OR upvotes <= 0 THEN + 0 + ELSE + ( + upvotes + downvotes) * CASE WHEN upvotes > downvotes THEN + downvotes::float / upvotes::float + ELSE + upvotes::float / downvotes::float + END + END; + +CREATE FUNCTION r.hot_rank (score numeric, published timestamp with time zone) + RETURNS double precision + LANGUAGE sql + IMMUTABLE PARALLEL SAFE RETURN + -- after a week, it will default to 0. + CASE WHEN ( +now() - published) > '0 days' + AND ( +now() - published) < '7 days' THEN + -- Use greatest(2,score), so that the hot_rank will be positive and not ignored. + log ( + greatest (2, score + 2)) / power (((EXTRACT(EPOCH FROM (now() - published)) / 3600) + 2), 1.8) + ELSE + -- if the post is from the future, set hot score to 0. otherwise you can game the post to + -- always be on top even with only 1 vote by setting it to the future + 0.0 + END; + +CREATE FUNCTION r.scaled_rank (score numeric, published timestamp with time zone, users_active_month numeric) + RETURNS double precision + LANGUAGE sql + IMMUTABLE PARALLEL SAFE + -- Add 2 to avoid divide by zero errors + -- Default for score = 1, active users = 1, and now, is (0.1728 / log(2 + 1)) = 0.3621 + -- There may need to be a scale factor multiplied to users_active_month, to make + -- the log curve less pronounced. This can be tuned in the future. + RETURN ( + r.hot_rank (score, published) / log(2 + users_active_month) +); + +-- For tables with `deleted` and `removed` columns, this function determines which rows to include in a count. +CREATE FUNCTION r.is_counted (item record) + RETURNS bool + LANGUAGE plpgsql + IMMUTABLE PARALLEL SAFE + AS $$ +BEGIN + RETURN COALESCE(NOT (item.deleted + OR item.removed), FALSE); +END; +$$; + +-- This function creates statement-level triggers for all operation types. It's designed this way +-- because of these limitations: +-- * A trigger that uses transition tables can only handle 1 operation type. +-- * Transition tables must be relevant for the operation type (for example, `NEW TABLE` is +-- not allowed for a `DELETE` trigger) +-- * Transition tables are only provided to the trigger function, not to functions that it calls. +-- +-- This function can only be called once per table. The trigger function body given as the 2nd argument +-- and can contain these names, which are replaced with a `SELECT` statement in parenthesis if needed: +-- * `select_old_rows` +-- * `select_new_rows` +-- * `select_old_and_new_rows` with 2 columns: +-- 1. `count_diff`: `-1` for old rows and `1` for new rows, which can be used with `sum` to get the number +-- to add to a count +-- 2. (same name as the trigger's table): the old or new row as a composite value +CREATE PROCEDURE r.create_triggers (table_name text, function_body text) +LANGUAGE plpgsql +AS $a$ +DECLARE + defs text := $$ + -- Delete + CREATE FUNCTION r.thing_delete_statement () + RETURNS TRIGGER + LANGUAGE plpgsql + AS function_body_delete; + CREATE TRIGGER delete_statement + AFTER DELETE ON thing REFERENCING OLD TABLE AS select_old_rows + FOR EACH STATEMENT + EXECUTE FUNCTION r.thing_delete_statement ( ); + -- Insert + CREATE FUNCTION r.thing_insert_statement ( ) + RETURNS TRIGGER + LANGUAGE plpgsql + AS function_body_insert; + CREATE TRIGGER insert_statement + AFTER INSERT ON thing REFERENCING NEW TABLE AS select_new_rows + FOR EACH STATEMENT + EXECUTE FUNCTION r.thing_insert_statement ( ); + -- Update + CREATE FUNCTION r.thing_update_statement ( ) + RETURNS TRIGGER + LANGUAGE plpgsql + AS function_body_update; + CREATE TRIGGER update_statement + AFTER UPDATE ON thing REFERENCING OLD TABLE AS select_old_rows NEW TABLE AS select_new_rows + FOR EACH STATEMENT + EXECUTE FUNCTION r.thing_update_statement ( ); + $$; + select_old_and_new_rows text := $$ ( + SELECT + -1 AS count_diff, + old_table::thing AS thing + FROM + select_old_rows AS old_table + UNION ALL + SELECT + 1 AS count_diff, + new_table::thing AS thing + FROM + select_new_rows AS new_table) $$; + empty_select_new_rows text := $$ ( + SELECT + * + FROM + -- Real transition table + select_old_rows + WHERE + FALSE) $$; + empty_select_old_rows text := $$ ( + SELECT + * + FROM + -- Real transition table + select_new_rows + WHERE + FALSE) $$; + BEGIN + function_body := replace(function_body, 'select_old_and_new_rows', select_old_and_new_rows); + -- `select_old_rows` and `select_new_rows` are made available as empty tables if they don't already exist + defs := replace(defs, 'function_body_delete', quote_literal(replace(function_body, 'select_new_rows', empty_select_new_rows))); + defs := replace(defs, 'function_body_insert', quote_literal(replace(function_body, 'select_old_rows', empty_select_old_rows))); + defs := replace(defs, 'function_body_update', quote_literal(function_body)); + defs := replace(defs, 'thing', table_name); + EXECUTE defs; +END; +$a$; + diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 2120c7f38..915d17b1d 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -1,5 +1,6 @@ use crate::{ aggregates::structs::CommentAggregates, + diesel::OptionalExtension, newtypes::CommentId, schema::comment_aggregates, utils::{functions::hot_rank, get_conn, DbPool}, @@ -8,12 +9,13 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl CommentAggregates { - pub async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result, Error> { let conn = &mut get_conn(pool).await?; comment_aggregates::table .find(comment_id) - .first::(conn) + .first(conn) .await + .optional() } pub async fn update_hot_rank( @@ -125,6 +127,7 @@ mod tests { let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id) .await + .unwrap() .unwrap(); assert_eq!(1, comment_aggs_before_delete.score); @@ -143,6 +146,7 @@ mod tests { let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id) .await + .unwrap() .unwrap(); assert_eq!(0, comment_aggs_after_dislike.score); @@ -155,6 +159,7 @@ mod tests { .unwrap(); let after_like_remove = CommentAggregates::read(pool, inserted_comment.id) .await + .unwrap() .unwrap(); assert_eq!(-1, after_like_remove.score); assert_eq!(0, after_like_remove.upvotes); @@ -164,8 +169,10 @@ mod tests { Post::delete(pool, inserted_post.id).await.unwrap(); // Should be none found, since the post was deleted - let after_delete = CommentAggregates::read(pool, inserted_comment.id).await; - assert!(after_delete.is_err()); + let after_delete = CommentAggregates::read(pool, inserted_comment.id) + .await + .unwrap(); + assert!(after_delete.is_none()); // This should delete all the associated rows, and fire triggers Person::delete(pool, another_inserted_person.id) diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index f1f54663b..0cf63809d 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -1,5 +1,6 @@ use crate::{ aggregates::structs::CommunityAggregates, + diesel::OptionalExtension, newtypes::CommunityId, schema::{community_aggregates, community_aggregates::subscribers}, utils::{get_conn, DbPool}, @@ -8,12 +9,16 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl CommunityAggregates { - pub async fn read(pool: &mut DbPool<'_>, for_community_id: CommunityId) -> Result { + pub async fn read( + pool: &mut DbPool<'_>, + for_community_id: CommunityId, + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; community_aggregates::table .find(for_community_id) - .first::(conn) + .first(conn) .await + .optional() } pub async fn update_federated_followers( @@ -25,7 +30,7 @@ impl CommunityAggregates { let new_subscribers: i64 = new_subscribers.into(); diesel::update(community_aggregates::table.find(for_community_id)) .set(subscribers.eq(new_subscribers)) - .get_result::(conn) + .get_result(conn) .await } } @@ -153,6 +158,7 @@ mod tests { let community_aggregates_before_delete = CommunityAggregates::read(pool, inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(2, community_aggregates_before_delete.subscribers); @@ -163,6 +169,7 @@ mod tests { // Test the other community let another_community_aggs = CommunityAggregates::read(pool, another_inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(1, another_community_aggs.subscribers); assert_eq!(1, another_community_aggs.subscribers_local); @@ -175,6 +182,7 @@ mod tests { .unwrap(); let after_unfollow = CommunityAggregates::read(pool, inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(1, after_unfollow.subscribers); assert_eq!(1, after_unfollow.subscribers_local); @@ -185,6 +193,7 @@ mod tests { .unwrap(); let after_follow_again = CommunityAggregates::read(pool, inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(2, after_follow_again.subscribers); assert_eq!(2, after_follow_again.subscribers_local); @@ -193,6 +202,7 @@ mod tests { Post::delete(pool, inserted_post.id).await.unwrap(); let after_parent_post_delete = CommunityAggregates::read(pool, inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(0, after_parent_post_delete.comments); assert_eq!(0, after_parent_post_delete.posts); @@ -203,6 +213,7 @@ mod tests { .unwrap(); let after_person_delete = CommunityAggregates::read(pool, inserted_community.id) .await + .unwrap() .unwrap(); assert_eq!(1, after_person_delete.subscribers); assert_eq!(1, after_person_delete.subscribers_local); @@ -223,7 +234,9 @@ mod tests { 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; - assert!(after_delete.is_err()); + let after_delete = CommunityAggregates::read(pool, inserted_community.id) + .await + .unwrap(); + assert!(after_delete.is_none()); } } diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 520b45225..03295173f 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -1,3 +1,4 @@ +pub(crate) use crate::diesel::OptionalExtension; use crate::{ aggregates::structs::PersonAggregates, newtypes::PersonId, @@ -8,12 +9,13 @@ use diesel::{result::Error, QueryDsl}; use diesel_async::RunQueryDsl; impl PersonAggregates { - pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; person_aggregates::table .find(person_id) - .first::(conn) + .first(conn) .await + .optional() } } @@ -127,6 +129,7 @@ mod tests { let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); assert_eq!(1, person_aggregates_before_delete.post_count); @@ -140,6 +143,7 @@ mod tests { .unwrap(); let after_post_like_remove = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); assert_eq!(0, after_post_like_remove.post_score); @@ -166,6 +170,7 @@ mod tests { let after_parent_comment_removed = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); assert_eq!(0, after_parent_comment_removed.comment_count); // TODO: fix person aggregate comment score calculation @@ -178,6 +183,7 @@ mod tests { .unwrap(); let after_parent_comment_delete = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); assert_eq!(0, after_parent_comment_delete.comment_count); // TODO: fix person aggregate comment score calculation @@ -193,6 +199,7 @@ mod tests { CommentLike::like(pool, &comment_like).await.unwrap(); let after_comment_add = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); assert_eq!(2, after_comment_add.comment_count); // TODO: fix person aggregate comment score calculation @@ -201,6 +208,7 @@ mod tests { Post::delete(pool, inserted_post.id).await.unwrap(); let after_post_delete = PersonAggregates::read(pool, inserted_person.id) .await + .unwrap() .unwrap(); // TODO: fix person aggregate comment score calculation // assert_eq!(0, after_post_delete.comment_score); @@ -222,8 +230,10 @@ mod tests { assert_eq!(1, community_num_deleted); // Should be none found - let after_delete = PersonAggregates::read(pool, inserted_person.id).await; - assert!(after_delete.is_err()); + let after_delete = PersonAggregates::read(pool, inserted_person.id) + .await + .unwrap(); + assert!(after_delete.is_none()); Instance::delete(pool, inserted_instance.id).await.unwrap(); } diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 7657dae9e..f6e108ee9 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -1,5 +1,6 @@ use crate::{ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, + diesel::OptionalExtension, newtypes::{PersonId, PostId}, schema::person_post_aggregates::dsl::{person_id, person_post_aggregates, post_id}, utils::{get_conn, DbPool}, @@ -25,11 +26,12 @@ impl PersonPostAggregates { pool: &mut DbPool<'_>, person_id_: PersonId, post_id_: PostId, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; person_post_aggregates .find((person_id_, post_id_)) - .first::(conn) + .first(conn) .await + .optional() } } diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index 7f95dc05a..cb8227795 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -1,5 +1,6 @@ use crate::{ aggregates::structs::PostAggregates, + diesel::OptionalExtension, newtypes::PostId, schema::{community_aggregates, post, post_aggregates}, utils::{ @@ -12,12 +13,13 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; impl PostAggregates { - pub async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result, Error> { let conn = &mut get_conn(pool).await?; post_aggregates::table .find(post_id) - .first::(conn) + .first(conn) .await + .optional() } pub async fn update_ranks(pool: &mut DbPool<'_>, post_id: PostId) -> Result { @@ -141,7 +143,10 @@ mod tests { PostLike::like(pool, &post_like).await.unwrap(); - let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(2, post_aggs_before_delete.comments); assert_eq!(1, post_aggs_before_delete.score); @@ -157,7 +162,10 @@ mod tests { PostLike::like(pool, &post_dislike).await.unwrap(); - let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(2, post_aggs_after_dislike.comments); assert_eq!(0, post_aggs_after_dislike.score); @@ -169,7 +177,10 @@ mod tests { Comment::delete(pool, inserted_child_comment.id) .await .unwrap(); - let after_comment_delete = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let after_comment_delete = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(0, after_comment_delete.comments); assert_eq!(0, after_comment_delete.score); assert_eq!(1, after_comment_delete.upvotes); @@ -179,7 +190,10 @@ mod tests { PostLike::remove(pool, inserted_person.id, inserted_post.id) .await .unwrap(); - let after_like_remove = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let after_like_remove = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(0, after_like_remove.comments); assert_eq!(-1, after_like_remove.score); assert_eq!(0, after_like_remove.upvotes); @@ -199,8 +213,8 @@ mod tests { 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; - assert!(after_delete.is_err()); + let after_delete = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + assert!(after_delete.is_none()); Instance::delete(pool, inserted_instance.id).await.unwrap(); } @@ -248,7 +262,10 @@ mod tests { let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); - let post_aggregates_before = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggregates_before = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(1, post_aggregates_before.comments); Comment::update( @@ -262,7 +279,10 @@ mod tests { .await .unwrap(); - let post_aggregates_after_remove = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggregates_after_remove = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(0, post_aggregates_after_remove.comments); Comment::update( @@ -287,7 +307,10 @@ mod tests { .await .unwrap(); - let post_aggregates_after_delete = PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggregates_after_delete = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(0, post_aggregates_after_delete.comments); Comment::update( @@ -301,8 +324,10 @@ mod tests { .await .unwrap(); - let post_aggregates_after_delete_remove = - PostAggregates::read(pool, inserted_post.id).await.unwrap(); + let post_aggregates_after_delete_remove = PostAggregates::read(pool, inserted_post.id) + .await + .unwrap() + .unwrap(); assert_eq!(0, post_aggregates_after_delete_remove.comments); Comment::delete(pool, inserted_comment.id).await.unwrap(); diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index b179ff7c7..268a37aac 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -1,5 +1,6 @@ use crate::{ aggregates::structs::SiteAggregates, + diesel::OptionalExtension, schema::site_aggregates, utils::{get_conn, DbPool}, }; @@ -7,9 +8,9 @@ use diesel::result::Error; use diesel_async::RunQueryDsl; impl SiteAggregates { - pub async fn read(pool: &mut DbPool<'_>) -> Result { + pub async fn read(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - site_aggregates::table.first::(conn).await + site_aggregates::table.first(conn).await.optional() } } @@ -111,7 +112,7 @@ mod tests { .await .unwrap(); - let site_aggregates_before_delete = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_before_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); // TODO: this is unstable, sometimes it returns 0 users, sometimes 1 //assert_eq!(0, site_aggregates_before_delete.users); @@ -121,7 +122,7 @@ mod tests { // Try a post delete Post::delete(pool, inserted_post.id).await.unwrap(); - let site_aggregates_after_post_delete = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_after_post_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); assert_eq!(1, site_aggregates_after_post_delete.posts); assert_eq!(0, site_aggregates_after_post_delete.comments); @@ -140,8 +141,8 @@ mod tests { assert!(after_delete_creator.is_ok()); Site::delete(pool, inserted_site.id).await.unwrap(); - let after_delete_site = SiteAggregates::read(pool).await; - assert!(after_delete_site.is_err()); + let after_delete_site = SiteAggregates::read(pool).await.unwrap(); + assert!(after_delete_site.is_none()); Instance::delete(pool, inserted_instance.id).await.unwrap(); } @@ -155,7 +156,7 @@ mod tests { let (inserted_instance, inserted_person, inserted_site, inserted_community) = prepare_site_with_community(pool).await; - let site_aggregates_before = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_before = SiteAggregates::read(pool).await.unwrap().unwrap(); assert_eq!(1, site_aggregates_before.communities); Community::update( @@ -169,7 +170,7 @@ mod tests { .await .unwrap(); - let site_aggregates_after_delete = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_after_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); assert_eq!(0, site_aggregates_after_delete.communities); Community::update( @@ -194,7 +195,7 @@ mod tests { .await .unwrap(); - let site_aggregates_after_remove = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_after_remove = SiteAggregates::read(pool).await.unwrap().unwrap(); assert_eq!(0, site_aggregates_after_remove.communities); Community::update( @@ -208,7 +209,7 @@ mod tests { .await .unwrap(); - let site_aggregates_after_remove_delete = SiteAggregates::read(pool).await.unwrap(); + let site_aggregates_after_remove_delete = SiteAggregates::read(pool).await.unwrap().unwrap(); assert_eq!(0, site_aggregates_after_remove_delete.communities); Community::delete(pool, inserted_community.id) diff --git a/crates/db_schema/src/impls/activity.rs b/crates/db_schema/src/impls/activity.rs index e1802c7c5..9391d55bc 100644 --- a/crates/db_schema/src/impls/activity.rs +++ b/crates/db_schema/src/impls/activity.rs @@ -22,18 +22,22 @@ impl SentActivity { .await } - pub async fn read_from_apub_id(pool: &mut DbPool<'_>, object_id: &DbUrl) -> Result { + pub async fn read_from_apub_id( + pool: &mut DbPool<'_>, + object_id: &DbUrl, + ) -> Result, Error> { 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) + .first(conn) .await + .optional() } - pub async fn read(pool: &mut DbPool<'_>, object_id: ActivityId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, object_id: ActivityId) -> Result, Error> { use crate::schema::sent_activity::dsl::sent_activity; let conn = &mut get_conn(pool).await?; - sent_activity.find(object_id).first::(conn).await + sent_activity.find(object_id).first(conn).await.optional() } } @@ -81,12 +85,9 @@ mod tests { .unwrap() .into(); - // inserting activity for first time - let res = ReceivedActivity::create(pool, &ap_id).await; - assert!(res.is_ok()); - - let res = ReceivedActivity::create(pool, &ap_id).await; - assert!(res.is_err()); + // inserting activity should only work once + ReceivedActivity::create(pool, &ap_id).await.unwrap(); + ReceivedActivity::create(pool, &ap_id).await.unwrap_err(); } #[tokio::test] @@ -118,7 +119,10 @@ mod tests { SentActivity::create(pool, form).await.unwrap(); - let res = SentActivity::read_from_apub_id(pool, &ap_id).await.unwrap(); + let res = SentActivity::read_from_apub_id(pool, &ap_id) + .await + .unwrap() + .unwrap(); assert_eq!(res.ap_id, ap_id); assert_eq!(res.data, data); assert_eq!(res.sensitive, sensitive); diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index a3071eaca..bd0fc0610 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -26,7 +26,7 @@ use diesel::{ QueryDsl, }; use diesel_async::{AsyncPgConnection, RunQueryDsl}; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use tokio::sync::OnceCell; pub const UNDETERMINED_ID: LanguageId = LanguageId(0); @@ -192,7 +192,7 @@ impl CommunityLanguage { pool: &mut DbPool<'_>, for_language_id: Option, for_community_id: CommunityId, - ) -> Result<(), LemmyError> { + ) -> LemmyResult<()> { use crate::schema::community_language::dsl::community_language; let conn = &mut get_conn(pool).await?; diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index b27d44591..30c058b89 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::{DecoratableTarget, OptionalExtension}, newtypes::{CommentId, DbUrl, PersonId}, schema::comment, source::comment::{ @@ -11,8 +12,9 @@ use crate::{ CommentUpdateForm, }, traits::{Crud, Likeable, Saveable}, - utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT}, + utils::{functions::coalesce, get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT}, }; +use chrono::{DateTime, Utc}; use diesel::{ dsl::{insert_into, sql_query}, result::Error, @@ -59,6 +61,15 @@ impl Comment { pool: &mut DbPool<'_>, comment_form: &CommentInsertForm, parent_path: Option<&Ltree>, + ) -> Result { + Self::insert_apub(pool, None, comment_form, parent_path).await + } + + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: Option>, + comment_form: &CommentInsertForm, + parent_path: Option<&Ltree>, ) -> Result { let conn = &mut get_conn(pool).await?; @@ -67,13 +78,21 @@ impl Comment { .run(|conn| { Box::pin(async move { // Insert, to get the id - let inserted_comment = insert_into(comment::table) - .values(comment_form) - .on_conflict(comment::ap_id) - .do_update() - .set(comment_form) - .get_result::(conn) - .await?; + let inserted_comment = if let Some(timestamp) = timestamp { + insert_into(comment::table) + .values(comment_form) + .on_conflict(comment::ap_id) + .filter_target(coalesce(comment::updated, comment::published).lt(timestamp)) + .do_update() + .set(comment_form) + .get_result::(conn) + .await? + } else { + insert_into(comment::table) + .values(comment_form) + .get_result::(conn) + .await? + }; let comment_id = inserted_comment.id; @@ -129,20 +148,18 @@ where ca.comment_id = c.id" }) .await } + pub async fn read_from_apub_id( pool: &mut DbPool<'_>, object_id: Url, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let object_id: DbUrl = object_id.into(); - Ok( - comment::table - .filter(comment::ap_id.eq(object_id)) - .first::(conn) - .await - .ok() - .map(Into::into), - ) + comment::table + .filter(comment::ap_id.eq(object_id)) + .first(conn) + .await + .optional() } pub fn parent_comment_id(&self) -> Option { @@ -380,7 +397,10 @@ mod tests { .await .unwrap(); - let read_comment = Comment::read(pool, inserted_comment.id).await.unwrap(); + 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(); diff --git a/crates/db_schema/src/impls/comment_reply.rs b/crates/db_schema/src/impls/comment_reply.rs index e6d0915e9..bfea74a5a 100644 --- a/crates/db_schema/src/impls/comment_reply.rs +++ b/crates/db_schema/src/impls/comment_reply.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::{CommentId, CommentReplyId, PersonId}, schema::comment_reply, source::comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm}, @@ -21,7 +22,7 @@ impl Crud for CommentReply { let conn = &mut get_conn(pool).await?; // since the return here isnt utilized, we dont need to do an update - // but get_result doesnt return the existing row here + // but get_result doesn't return the existing row here insert_into(comment_reply::table) .values(comment_reply_form) .on_conflict((comment_reply::recipient_id, comment_reply::comment_id)) @@ -63,25 +64,27 @@ impl CommentReply { pub async fn read_by_comment( pool: &mut DbPool<'_>, for_comment_id: CommentId, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; comment_reply::table .filter(comment_reply::comment_id.eq(for_comment_id)) - .first::(conn) + .first(conn) .await + .optional() } pub async fn read_by_comment_and_person( pool: &mut DbPool<'_>, for_comment_id: CommentId, for_recipient_id: PersonId, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; comment_reply::table .filter(comment_reply::comment_id.eq(for_comment_id)) .filter(comment_reply::recipient_id.eq(for_recipient_id)) - .first::(conn) + .first(conn) .await + .optional() } } @@ -174,7 +177,10 @@ mod tests { published: inserted_reply.published, }; - let read_reply = CommentReply::read(pool, inserted_reply.id).await.unwrap(); + let read_reply = CommentReply::read(pool, inserted_reply.id) + .await + .unwrap() + .unwrap(); let comment_reply_update_form = CommentReplyUpdateForm { read: Some(false) }; let updated_reply = CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index af3bf9bcf..e886098ce 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::{DecoratableTarget, OptionalExtension}, newtypes::{CommunityId, DbUrl, PersonId}, schema::{community, community_follower, instance}, source::{ @@ -17,9 +18,14 @@ use crate::{ post::Post, }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, - utils::{functions::lower, get_conn, DbPool}, + utils::{ + functions::{coalesce, lower}, + get_conn, + DbPool, + }, SubscribedType, }; +use chrono::{DateTime, Utc}; use diesel::{ deserialize, dsl, @@ -44,25 +50,15 @@ impl Crud for Community { type IdType = CommunityId; async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { - let is_new_community = match &form.actor_id { - Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(), - None => true, - }; let conn = &mut get_conn(pool).await?; - // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible let community_ = insert_into(community::table) .values(form) - .on_conflict(community::actor_id) - .do_update() - .set(form) .get_result::(conn) .await?; // Initialize languages for new community - if is_new_community { - CommunityLanguage::update(pool, vec![], community_.id).await?; - } + CommunityLanguage::update(pool, vec![], community_.id).await?; Ok(community_) } @@ -116,29 +112,63 @@ pub enum CollectionType { } impl Community { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &CommunityInsertForm, + ) -> Result { + let is_new_community = match &form.actor_id { + Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(), + None => true, + }; + let conn = &mut get_conn(pool).await?; + + // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible + let community_ = insert_into(community::table) + .values(form) + .on_conflict(community::actor_id) + .filter_target(coalesce(community::updated, community::published).lt(timestamp)) + .do_update() + .set(form) + .get_result::(conn) + .await?; + + // Initialize languages for new community + if is_new_community { + CommunityLanguage::update(pool, vec![], community_.id).await?; + } + + Ok(community_) + } + /// Get the community which has a given moderators or featured url, also return the collection type pub async fn get_by_collection_url( pool: &mut DbPool<'_>, url: &DbUrl, - ) -> Result<(Community, CollectionType), Error> { + ) -> Result, Error> { use crate::schema::community::dsl::{featured_url, moderators_url}; use CollectionType::*; let conn = &mut get_conn(pool).await?; let res = community::table .filter(moderators_url.eq(url)) - .first::(conn) - .await; - if let Ok(c) = res { - return Ok((c, Moderators)); - } - let res = community::table - .filter(featured_url.eq(url)) - .first::(conn) - .await; - if let Ok(c) = res { - return Ok((c, Featured)); + .first(conn) + .await + .optional()?; + + if let Some(c) = res { + Ok(Some((c, Moderators))) + } else { + let res = community::table + .filter(featured_url.eq(url)) + .first(conn) + .await + .optional()?; + if let Some(c) = res { + Ok(Some((c, Featured))) + } else { + Ok(None) + } } - Err(diesel::NotFound) } pub async fn set_featured_posts( @@ -323,21 +353,18 @@ impl ApubActor for Community { object_id: &DbUrl, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - Ok( - community::table - .filter(community::actor_id.eq(object_id)) - .first::(conn) - .await - .ok() - .map(Into::into), - ) + community::table + .filter(community::actor_id.eq(object_id)) + .first(conn) + .await + .optional() } async fn read_from_name( pool: &mut DbPool<'_>, community_name: &str, include_deleted: bool, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let mut q = community::table .into_boxed() @@ -348,22 +375,23 @@ impl ApubActor for Community { .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)); } - q.first::(conn).await + q.first(conn).await.optional() } async fn read_from_name_and_domain( pool: &mut DbPool<'_>, community_name: &str, for_domain: &str, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; community::table .inner_join(instance::table) .filter(lower(community::name).eq(community_name.to_lowercase())) .filter(lower(instance::domain).eq(for_domain.to_lowercase())) .select(community::all_columns) - .first::(conn) + .first(conn) .await + .optional() } } @@ -499,7 +527,10 @@ mod tests { expires: None, }; - let read_community = Community::read(pool, inserted_community.id).await.unwrap(); + let read_community = Community::read(pool, inserted_community.id) + .await + .unwrap() + .unwrap(); let update_community_form = CommunityUpdateForm { title: Some("nada".to_owned()), diff --git a/crates/db_schema/src/impls/email_verification.rs b/crates/db_schema/src/impls/email_verification.rs index c5a8792fb..b4951cf73 100644 --- a/crates/db_schema/src/impls/email_verification.rs +++ b/crates/db_schema/src/impls/email_verification.rs @@ -16,6 +16,7 @@ use diesel::{ sql_types::Timestamptz, ExpressionMethods, IntoSql, + OptionalExtension, QueryDsl, }; use diesel_async::RunQueryDsl; @@ -25,17 +26,18 @@ impl EmailVerification { let conn = &mut get_conn(pool).await?; insert_into(email_verification) .values(form) - .get_result::(conn) + .get_result(conn) .await } - pub async fn read_for_token(pool: &mut DbPool<'_>, token: &str) -> Result { + pub async fn read_for_token(pool: &mut DbPool<'_>, token: &str) -> Result, Error> { let conn = &mut get_conn(pool).await?; email_verification .filter(verification_token.eq(token)) .filter(published.gt(now.into_sql::() - 7.days())) - .first::(conn) + .first(conn) .await + .optional() } pub async fn delete_old_tokens_for_local_user( pool: &mut DbPool<'_>, diff --git a/crates/db_schema/src/impls/images.rs b/crates/db_schema/src/impls/images.rs index a9aeb1f16..9589aeee3 100644 --- a/crates/db_schema/src/impls/images.rs +++ b/crates/db_schema/src/impls/images.rs @@ -1,8 +1,8 @@ use crate::{ - newtypes::{DbUrl, LocalUserId}, + newtypes::DbUrl, schema::{local_image, remote_image}, source::images::{LocalImage, LocalImageForm, RemoteImage, RemoteImageForm}, - utils::{get_conn, limit_and_offset, DbPool}, + utils::{get_conn, DbPool}, }; use diesel::{ dsl::exists, @@ -25,55 +25,6 @@ impl LocalImage { .await } - pub async fn get_all_paged_by_local_user_id( - pool: &mut DbPool<'_>, - user_id: LocalUserId, - page: Option, - limit: Option, - ) -> Result, Error> { - Self::get_all_helper(pool, Some(user_id), page, limit, false).await - } - - pub async fn get_all_by_local_user_id( - pool: &mut DbPool<'_>, - user_id: LocalUserId, - ) -> Result, Error> { - Self::get_all_helper(pool, Some(user_id), None, None, true).await - } - - pub async fn get_all( - pool: &mut DbPool<'_>, - page: Option, - limit: Option, - ) -> Result, Error> { - Self::get_all_helper(pool, None, page, limit, false).await - } - - async fn get_all_helper( - pool: &mut DbPool<'_>, - user_id: Option, - page: Option, - limit: Option, - ignore_page_limits: bool, - ) -> Result, Error> { - let conn = &mut get_conn(pool).await?; - let mut query = local_image::table - .select(local_image::all_columns) - .order_by(local_image::published.desc()) - .into_boxed(); - - if let Some(user_id) = user_id { - query = query.filter(local_image::local_user_id.eq(user_id)) - } - - if !ignore_page_limits { - let (limit, offset) = limit_and_offset(page, limit)?; - query = query.limit(limit).offset(offset); - } - - query.load::(conn).await - } - pub async fn delete_by_alias(pool: &mut DbPool<'_>, alias: &str) -> Result { let conn = &mut get_conn(pool).await?; diesel::delete(local_image::table.filter(local_image::pictrs_alias.eq(alias))) diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index c329d8cc0..67da14823 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -26,13 +26,14 @@ use diesel::{ result::Error, ExpressionMethods, NullableExpressionMethods, + OptionalExtension, QueryDsl, SelectableHelper, }; use diesel_async::RunQueryDsl; impl Instance { - /// Attempt to read Instance column for the given domain. If it doesnt exist, insert a new one. + /// Attempt to read Instance column for the given domain. If it doesn't exist, insert a new one. /// There is no need for update as the domain of an existing instance cant change. pub async fn read_or_create(pool: &mut DbPool<'_>, domain_: String) -> Result { use crate::schema::instance::domain; @@ -41,11 +42,14 @@ impl Instance { // First try to read the instance row and return directly if found let instance = instance::table .filter(lower(domain).eq(&domain_.to_lowercase())) - .first::(conn) - .await; + .first(conn) + .await + .optional()?; + + // TODO could convert this to unwrap_or_else once async closures are stable match instance { - Ok(i) => Ok(i), - Err(diesel::NotFound) => { + Some(i) => Ok(i), + None => { // Instance not in database yet, insert it let form = InstanceForm::builder() .domain(domain_) @@ -61,7 +65,6 @@ impl Instance { .get_result::(conn) .await } - e => e, } } pub async fn update( diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs index 905221402..6a7b4e9ac 100644 --- a/crates/db_schema/src/impls/language.rs +++ b/crates/db_schema/src/impls/language.rs @@ -1,7 +1,7 @@ use crate::{ diesel::ExpressionMethods, newtypes::LanguageId, - schema::language::dsl::{code, language}, + schema::language, source::language::Language, utils::{get_conn, DbPool}, }; @@ -9,14 +9,14 @@ use diesel::{result::Error, QueryDsl}; use diesel_async::RunQueryDsl; impl Language { - pub async fn read_all(pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn read_all(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - language.load::(conn).await + language::table.load(conn).await } - pub async fn read_from_id(pool: &mut DbPool<'_>, id_: LanguageId) -> Result { + pub async fn read_from_id(pool: &mut DbPool<'_>, id_: LanguageId) -> Result { let conn = &mut get_conn(pool).await?; - language.find(id_).first::(conn).await + language::table.find(id_).first(conn).await } /// Attempts to find the given language code and return its ID. If not found, returns none. @@ -27,8 +27,8 @@ impl Language { if let Some(code_) = code_ { let conn = &mut get_conn(pool).await?; Ok( - language - .filter(code.eq(code_)) + language::table + .filter(language::code.eq(code_)) .first::(conn) .await .map(|l| l.id) diff --git a/crates/db_schema/src/impls/local_site.rs b/crates/db_schema/src/impls/local_site.rs index 5a6cf2a53..602dfe1f4 100644 --- a/crates/db_schema/src/impls/local_site.rs +++ b/crates/db_schema/src/impls/local_site.rs @@ -1,47 +1,47 @@ use crate::{ - schema::local_site::dsl::local_site, + schema::local_site, source::local_site::{LocalSite, LocalSiteInsertForm, LocalSiteUpdateForm}, utils::{get_conn, DbPool}, }; use diesel::{dsl::insert_into, result::Error}; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyError, CACHE_DURATION_SHORT}; +use lemmy_utils::{error::LemmyResult, CACHE_DURATION_API}; use moka::future::Cache; use once_cell::sync::Lazy; impl LocalSite { pub async fn create(pool: &mut DbPool<'_>, form: &LocalSiteInsertForm) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(local_site) + insert_into(local_site::table) .values(form) .get_result::(conn) .await } - pub async fn read(pool: &mut DbPool<'_>) -> Result { + pub async fn read(pool: &mut DbPool<'_>) -> LemmyResult { static CACHE: Lazy> = Lazy::new(|| { Cache::builder() .max_capacity(1) - .time_to_live(CACHE_DURATION_SHORT) + .time_to_live(CACHE_DURATION_API) .build() }); Ok( CACHE .try_get_with((), async { let conn = &mut get_conn(pool).await?; - local_site.first::(conn).await + local_site::table.first(conn).await }) .await?, ) } pub async fn update(pool: &mut DbPool<'_>, form: &LocalSiteUpdateForm) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(local_site) + diesel::update(local_site::table) .set(form) .get_result::(conn) .await } pub async fn delete(pool: &mut DbPool<'_>) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete(local_site).execute(conn).await + diesel::delete(local_site::table).execute(conn).await } } diff --git a/crates/db_schema/src/impls/local_site_rate_limit.rs b/crates/db_schema/src/impls/local_site_rate_limit.rs index 0c9e96e0b..6ab9ca8b8 100644 --- a/crates/db_schema/src/impls/local_site_rate_limit.rs +++ b/crates/db_schema/src/impls/local_site_rate_limit.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, schema::local_site_rate_limit, source::local_site_rate_limit::{ LocalSiteRateLimit, @@ -11,9 +12,9 @@ use diesel::{dsl::insert_into, result::Error}; use diesel_async::RunQueryDsl; impl LocalSiteRateLimit { - pub async fn read(pool: &mut DbPool<'_>) -> Result { + pub async fn read(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - local_site_rate_limit::table.first::(conn).await + local_site_rate_limit::table.first(conn).await.optional() } pub async fn create( diff --git a/crates/db_schema/src/impls/local_user_vote_display_mode.rs b/crates/db_schema/src/impls/local_user_vote_display_mode.rs index 4458fc1b6..d77502335 100644 --- a/crates/db_schema/src/impls/local_user_vote_display_mode.rs +++ b/crates/db_schema/src/impls/local_user_vote_display_mode.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::LocalUserId, schema::local_user_vote_display_mode, source::local_user_vote_display_mode::{ @@ -12,11 +13,12 @@ use diesel::{dsl::insert_into, result::Error, QueryDsl}; use diesel_async::RunQueryDsl; impl LocalUserVoteDisplayMode { - pub async fn read(pool: &mut DbPool<'_>) -> Result { + pub async fn read(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; local_user_vote_display_mode::table - .first::(conn) + .first(conn) .await + .optional() } pub async fn create( diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index 99f117878..125bbcd51 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -568,6 +568,7 @@ mod tests { .unwrap(); let read_mod_remove_post = ModRemovePost::read(pool, inserted_mod_remove_post.id) .await + .unwrap() .unwrap(); let expected_mod_remove_post = ModRemovePost { id: inserted_mod_remove_post.id, @@ -590,6 +591,7 @@ mod tests { .unwrap(); let read_mod_lock_post = ModLockPost::read(pool, inserted_mod_lock_post.id) .await + .unwrap() .unwrap(); let expected_mod_lock_post = ModLockPost { id: inserted_mod_lock_post.id, @@ -612,6 +614,7 @@ mod tests { .unwrap(); let read_mod_feature_post = ModFeaturePost::read(pool, inserted_mod_feature_post.id) .await + .unwrap() .unwrap(); let expected_mod_feature_post = ModFeaturePost { id: inserted_mod_feature_post.id, @@ -635,6 +638,7 @@ mod tests { .unwrap(); let read_mod_remove_comment = ModRemoveComment::read(pool, inserted_mod_remove_comment.id) .await + .unwrap() .unwrap(); let expected_mod_remove_comment = ModRemoveComment { id: inserted_mod_remove_comment.id, @@ -660,6 +664,7 @@ mod tests { let read_mod_remove_community = ModRemoveCommunity::read(pool, inserted_mod_remove_community.id) .await + .unwrap() .unwrap(); let expected_mod_remove_community = ModRemoveCommunity { id: inserted_mod_remove_community.id, @@ -687,6 +692,7 @@ mod tests { let read_mod_ban_from_community = ModBanFromCommunity::read(pool, inserted_mod_ban_from_community.id) .await + .unwrap() .unwrap(); let expected_mod_ban_from_community = ModBanFromCommunity { id: inserted_mod_ban_from_community.id, @@ -709,7 +715,10 @@ mod tests { 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(); + let read_mod_ban = ModBan::read(pool, inserted_mod_ban.id) + .await + .unwrap() + .unwrap(); let expected_mod_ban = ModBan { id: inserted_mod_ban.id, mod_person_id: inserted_mod.id, @@ -733,6 +742,7 @@ mod tests { .unwrap(); let read_mod_add_community = ModAddCommunity::read(pool, inserted_mod_add_community.id) .await + .unwrap() .unwrap(); let expected_mod_add_community = ModAddCommunity { id: inserted_mod_add_community.id, @@ -751,7 +761,10 @@ mod tests { 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(); + let read_mod_add = ModAdd::read(pool, inserted_mod_add.id) + .await + .unwrap() + .unwrap(); let expected_mod_add = ModAdd { id: inserted_mod_add.id, mod_person_id: inserted_mod.id, diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index 491097135..762aa4656 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::LocalUserId, schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token}, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, @@ -54,16 +55,14 @@ impl PasswordResetRequest { Self::create(pool, &form).await } - pub async fn read_from_token( - pool: &mut DbPool<'_>, - token_: &str, - ) -> Result { + pub async fn read_from_token(pool: &mut DbPool<'_>, token_: &str) -> Result, Error> { let conn = &mut get_conn(pool).await?; password_reset_request .filter(token.eq(token_)) .filter(published.gt(now.into_sql::() - 1.days())) - .first::(conn) + .first(conn) .await + .optional() } pub async fn get_recent_password_resets_count( @@ -141,6 +140,7 @@ mod tests { let read_password_reset_request = PasswordResetRequest::read_from_token(pool, token) .await + .unwrap() .unwrap(); let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); Instance::delete(pool, inserted_instance.id).await.unwrap(); diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index c90f67891..a4c7073da 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, schema::{comment, community, instance, local_user, person, person_follower, post}, source::person::{ @@ -19,13 +20,16 @@ impl Crud for Person { type InsertForm = PersonInsertForm; type UpdateForm = PersonUpdateForm; type IdType = PersonId; - async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + + // Override this, so that you don't get back deleted + async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; person::table .filter(person::deleted.eq(false)) .find(person_id) - .first::(conn) + .first(conn) .await + .optional() } async fn create(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { @@ -51,7 +55,7 @@ impl Crud for Person { impl Person { /// Update or insert the person. /// - /// This is necessary for federation, because Activitypub doesnt distinguish between these actions. + /// This is necessary for federation, because Activitypub doesn't distinguish between these actions. pub async fn upsert(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result { let conn = &mut get_conn(pool).await?; insert_into(person::table) @@ -126,22 +130,19 @@ impl ApubActor for Person { object_id: &DbUrl, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - Ok( - person::table - .filter(person::deleted.eq(false)) - .filter(person::actor_id.eq(object_id)) - .first::(conn) - .await - .ok() - .map(Into::into), - ) + person::table + .filter(person::deleted.eq(false)) + .filter(person::actor_id.eq(object_id)) + .first(conn) + .await + .optional() } async fn read_from_name( pool: &mut DbPool<'_>, from_name: &str, include_deleted: bool, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let mut q = person::table .into_boxed() @@ -150,14 +151,14 @@ impl ApubActor for Person { if !include_deleted { q = q.filter(person::deleted.eq(false)) } - q.first::(conn).await + q.first(conn).await.optional() } async fn read_from_name_and_domain( pool: &mut DbPool<'_>, person_name: &str, for_domain: &str, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; person::table @@ -165,8 +166,9 @@ impl ApubActor for Person { .filter(lower(person::name).eq(person_name.to_lowercase())) .filter(lower(instance::domain).eq(for_domain.to_lowercase())) .select(person::all_columns) - .first::(conn) + .first(conn) .await + .optional() } } @@ -269,7 +271,10 @@ mod tests { instance_id: inserted_instance.id, }; - let read_person = Person::read(pool, inserted_person.id).await.unwrap(); + let read_person = Person::read(pool, inserted_person.id) + .await + .unwrap() + .unwrap(); let update_person_form = PersonUpdateForm { actor_id: Some(inserted_person.actor_id.clone()), diff --git a/crates/db_schema/src/impls/person_mention.rs b/crates/db_schema/src/impls/person_mention.rs index 5524f87ee..c08019170 100644 --- a/crates/db_schema/src/impls/person_mention.rs +++ b/crates/db_schema/src/impls/person_mention.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::{CommentId, PersonId, PersonMentionId}, schema::person_mention, source::person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm}, @@ -20,7 +21,7 @@ impl Crud for PersonMention { ) -> Result { let conn = &mut get_conn(pool).await?; // since the return here isnt utilized, we dont need to do an update - // but get_result doesnt return the existing row here + // but get_result doesn't return the existing row here insert_into(person_mention::table) .values(person_mention_form) .on_conflict((person_mention::recipient_id, person_mention::comment_id)) @@ -63,13 +64,14 @@ impl PersonMention { pool: &mut DbPool<'_>, for_comment_id: CommentId, for_recipient_id: PersonId, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; person_mention::table .filter(person_mention::comment_id.eq(for_comment_id)) .filter(person_mention::recipient_id.eq(for_recipient_id)) - .first::(conn) + .first(conn) .await + .optional() } } @@ -164,6 +166,7 @@ mod tests { let read_mention = PersonMention::read(pool, inserted_mention.id) .await + .unwrap() .unwrap(); let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) }; diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 8db871ad5..2d055b1a8 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId, PostId}, schema::{post, post_hide, post_like, post_read, post_saved}, source::post::{ @@ -27,8 +28,15 @@ use crate::{ }, }; use ::url::Url; -use chrono::Utc; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods}; +use chrono::{DateTime, Utc}; +use diesel::{ + dsl::insert_into, + result::Error, + DecoratableTarget, + ExpressionMethods, + QueryDsl, + TextExpressionMethods, +}; use diesel_async::RunQueryDsl; use std::collections::HashSet; @@ -42,9 +50,6 @@ impl Crud for Post { let conn = &mut get_conn(pool).await?; insert_into(post::table) .values(form) - .on_conflict(post::ap_id) - .do_update() - .set(form) .get_result::(conn) .await } @@ -63,6 +68,22 @@ impl Crud for Post { } impl Post { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &PostInsertForm, + ) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(post::table) + .values(form) + .on_conflict(post::ap_id) + .filter_target(coalesce(post::updated, post::published).lt(timestamp)) + .do_update() + .set(form) + .get_result::(conn) + .await + } + pub async fn list_for_community( pool: &mut DbPool<'_>, the_community_id: CommunityId, @@ -162,14 +183,11 @@ impl Post { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let object_id: DbUrl = object_id.into(); - Ok( - post::table - .filter(post::ap_id.eq(object_id)) - .first::(conn) - .await - .ok() - .map(Into::into), - ) + post::table + .filter(post::ap_id.eq(object_id)) + .first(conn) + .await + .optional() } pub async fn fetch_pictrs_posts_for_creator( @@ -497,7 +515,7 @@ mod tests { .unwrap(); assert_eq!(2, marked_as_read); - let read_post = Post::read(pool, inserted_post.id).await.unwrap(); + let read_post = Post::read(pool, inserted_post.id).await.unwrap().unwrap(); let new_post_update = PostUpdateForm { name: Some("A test post".into()), diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index ee0009189..75c7ce9bc 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -1,13 +1,14 @@ use crate::{ + diesel::{DecoratableTarget, OptionalExtension}, newtypes::{DbUrl, PersonId, PrivateMessageId}, - schema::private_message::dsl::{ap_id, private_message, read, recipient_id}, + schema::private_message, source::private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm}, traits::Crud, - utils::{get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, }; +use chrono::{DateTime, Utc}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; -use lemmy_utils::error::LemmyError; use url::Url; #[async_trait] @@ -18,11 +19,8 @@ impl Crud for PrivateMessage { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(private_message) + insert_into(private_message::table) .values(form) - .on_conflict(ap_id) - .do_update() - .set(form) .get_result::(conn) .await } @@ -33,7 +31,7 @@ impl Crud for PrivateMessage { form: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(private_message.find(private_message_id)) + diesel::update(private_message::table.find(private_message_id)) .set(form) .get_result::(conn) .await @@ -41,17 +39,33 @@ impl Crud for PrivateMessage { } impl PrivateMessage { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &PrivateMessageInsertForm, + ) -> Result { + let conn = &mut get_conn(pool).await?; + insert_into(private_message::table) + .values(form) + .on_conflict(private_message::ap_id) + .filter_target(coalesce(private_message::updated, private_message::published).lt(timestamp)) + .do_update() + .set(form) + .get_result::(conn) + .await + } + pub async fn mark_all_as_read( pool: &mut DbPool<'_>, for_recipient_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; diesel::update( - private_message - .filter(recipient_id.eq(for_recipient_id)) - .filter(read.eq(false)), + private_message::table + .filter(private_message::recipient_id.eq(for_recipient_id)) + .filter(private_message::read.eq(false)), ) - .set(read.eq(true)) + .set(private_message::read.eq(true)) .get_results::(conn) .await } @@ -59,17 +73,14 @@ impl PrivateMessage { pub async fn read_from_apub_id( pool: &mut DbPool<'_>, object_id: Url, - ) -> Result, LemmyError> { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; let object_id: DbUrl = object_id.into(); - Ok( - private_message - .filter(ap_id.eq(object_id)) - .first::(conn) - .await - .ok() - .map(Into::into), - ) + private_message::table + .filter(private_message::ap_id.eq(object_id)) + .first(conn) + .await + .optional() } } @@ -141,6 +152,7 @@ mod tests { let read_private_message = PrivateMessage::read(pool, inserted_private_message.id) .await + .unwrap() .unwrap(); let private_message_update_form = PrivateMessageUpdateForm { diff --git a/crates/db_schema/src/impls/private_message_report.rs b/crates/db_schema/src/impls/private_message_report.rs index c20783db0..b5d8fd039 100644 --- a/crates/db_schema/src/impls/private_message_report.rs +++ b/crates/db_schema/src/impls/private_message_report.rs @@ -46,7 +46,7 @@ impl Reportable for PrivateMessageReport { .await } - // TODO: this is unused because private message doesnt have remove handler + // TODO: this is unused because private message doesn't have remove handler async fn resolve_all_for_object( _pool: &mut DbPool<'_>, _pm_id_: PrivateMessageId, diff --git a/crates/db_schema/src/impls/registration_application.rs b/crates/db_schema/src/impls/registration_application.rs index c4df7ba69..46b7d4bee 100644 --- a/crates/db_schema/src/impls/registration_application.rs +++ b/crates/db_schema/src/impls/registration_application.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::LocalUserId, schema::registration_application::dsl::{local_user_id, registration_application}, source::registration_application::{ @@ -43,11 +44,12 @@ impl RegistrationApplication { pub async fn find_by_local_user_id( pool: &mut DbPool<'_>, local_user_id_: LocalUserId, - ) -> Result { + ) -> Result, Error> { let conn = &mut get_conn(pool).await?; registration_application .filter(local_user_id.eq(local_user_id_)) - .first::(conn) + .first(conn) .await + .optional() } } diff --git a/crates/db_schema/src/impls/secret.rs b/crates/db_schema/src/impls/secret.rs index f21c6c487..1365ea838 100644 --- a/crates/db_schema/src/impls/secret.rs +++ b/crates/db_schema/src/impls/secret.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, schema::secret::dsl::secret, source::secret::Secret, utils::{get_conn, DbPool}, @@ -9,12 +10,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 { + pub async fn init(pool: &mut DbPool<'_>) -> Result, Error> { Self::read_secrets(pool).await } - async fn read_secrets(pool: &mut DbPool<'_>) -> Result { + async fn read_secrets(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - secret.first::(conn).await + secret.first(conn).await.optional() } } diff --git a/crates/db_schema/src/impls/site.rs b/crates/db_schema/src/impls/site.rs index 7e9329afb..a371f9e07 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::dsl::{actor_id, id, instance_id, site}, + schema::site, source::{ actor_language::SiteLanguage, site::{Site, SiteInsertForm, SiteUpdateForm}, @@ -19,7 +19,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 { + async fn read(_pool: &mut DbPool<'_>, _site_id: SiteId) -> Result, Error> { unimplemented!() } @@ -31,9 +31,9 @@ impl Crud for Site { let conn = &mut get_conn(pool).await?; // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible - let site_ = insert_into(site) + let site_ = insert_into(site::table) .values(form) - .on_conflict(actor_id) + .on_conflict(site::actor_id) .do_update() .set(form) .get_result::(conn) @@ -53,7 +53,7 @@ impl Crud for Site { new_site: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(site.find(site_id)) + diesel::update(site::table.find(site_id)) .set(new_site) .get_result::(conn) .await @@ -66,9 +66,9 @@ impl Site { _instance_id: InstanceId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - site - .filter(instance_id.eq(_instance_id)) - .get_result(conn) + site::table + .filter(site::instance_id.eq(_instance_id)) + .first(conn) .await .optional() } @@ -78,17 +78,20 @@ impl Site { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - site - .filter(actor_id.eq(object_id)) - .first::(conn) + site::table + .filter(site::actor_id.eq(object_id)) + .first(conn) .await .optional() - .map(Into::into) } pub async fn read_remote_sites(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - site.order_by(id).offset(1).get_results::(conn).await + site::table + .order_by(site::id) + .offset(1) + .get_results::(conn) + .await } /// Instance actor is at the root path, so we simply need to clear the path and other unnecessary diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 05663ff3e..37c44e263 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -43,6 +43,9 @@ pub mod traits; #[cfg(feature = "full")] pub mod utils; +#[cfg(feature = "full")] +mod schema_setup; + use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; #[cfg(feature = "full")] diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index 96fc23ac6..e0c516037 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -176,7 +176,7 @@ impl Display for DbUrl { } } -// the project doesnt compile with From +// the project doesn't compile with From #[allow(clippy::from_over_into)] impl Into for Url { fn into(self) -> DbUrl { diff --git a/crates/db_schema/src/schema_setup.rs b/crates/db_schema/src/schema_setup.rs new file mode 100644 index 000000000..dc8cdf387 --- /dev/null +++ b/crates/db_schema/src/schema_setup.rs @@ -0,0 +1,64 @@ +use anyhow::Context; +use diesel::{connection::SimpleConnection, Connection, PgConnection}; +use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; +use lemmy_utils::error::LemmyError; +use tracing::info; + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + +/// This SQL code sets up the `r` schema, which contains things that can be safely dropped and replaced +/// instead of being changed using migrations. It may not create or modify things outside of the `r` schema +/// (indicated by `r.` before the name), unless a comment says otherwise. +/// +/// Currently, this code is only run after the server starts and there's at least 1 pending migration +/// to run. This means every time you change something here, you must also create a migration (a blank +/// up.sql file works fine). This behavior will be removed when we implement a better way to avoid +/// useless schema updates and locks. +/// +/// If you add something that depends on something (such as a table) created in a new migration, then down.sql +/// must use `CASCADE` when dropping it. This doesn't need to be fixed in old migrations because the +/// "replaceable-schema" migration runs `DROP SCHEMA IF EXISTS r CASCADE` in down.sql. +const REPLACEABLE_SCHEMA: &[&str] = &[ + "DROP SCHEMA IF EXISTS r CASCADE;", + "CREATE SCHEMA r;", + include_str!("../replaceable_schema/utils.sql"), + include_str!("../replaceable_schema/triggers.sql"), +]; + +pub fn run(db_url: &str) -> Result<(), LemmyError> { + // Migrations don't support async connection + let mut conn = PgConnection::establish(db_url).with_context(|| "Error connecting to database")?; + + // Run all pending migrations except for the newest one, then run the newest one in the same transaction + // as `REPLACEABLE_SCHEMA`. This code will be becone less hacky when the conditional setup of things in + // `REPLACEABLE_SCHEMA` is done without using the number of pending migrations. + info!("Running Database migrations (This may take a long time)..."); + let migrations = conn + .pending_migrations(MIGRATIONS) + .map_err(|e| anyhow::anyhow!("Couldn't determine pending migrations: {e}"))?; + for migration in migrations.iter().rev().skip(1).rev() { + conn + .run_migration(migration) + .map_err(|e| anyhow::anyhow!("Couldn't run DB Migrations: {e}"))?; + } + conn.transaction::<_, LemmyError, _>(|conn| { + if let Some(migration) = migrations.last() { + // Migration is run with a savepoint since there's already a transaction + conn + .run_migration(migration) + .map_err(|e| anyhow::anyhow!("Couldn't run DB Migrations: {e}"))?; + } else if !cfg!(debug_assertions) { + // In production, skip running `REPLACEABLE_SCHEMA` to avoid locking things in the schema. In + // CI, always run it because `diesel migration` commands would otherwise prevent it. + return Ok(()); + } + conn + .batch_execute(&REPLACEABLE_SCHEMA.join("\n")) + .context("Couldn't run SQL files in crates/db_schema/replaceable_schema")?; + + Ok(()) + })?; + info!("Database migrations complete."); + + Ok(()) +} diff --git a/crates/db_schema/src/source/site.rs b/crates/db_schema/src/source/site.rs index 40ba14f96..83d23c779 100644 --- a/crates/db_schema/src/source/site.rs +++ b/crates/db_schema/src/source/site.rs @@ -36,7 +36,7 @@ pub struct Site { pub inbox_url: DbUrl, #[serde(skip)] pub private_key: Option, - #[serde(skip)] + // TODO: mark as `serde(skip)` in next major release as its not needed for api pub public_key: String, pub instance_id: InstanceId, /// If present, nsfw content is visible by default. Should be displayed by frontends/clients diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index b0434a65c..139ec0e15 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, utils::{get_conn, DbPool}, }; @@ -42,10 +43,10 @@ where async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result; - async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result { + async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result, Error> { let query: Find = Self::table().find(id); let conn = &mut *get_conn(pool).await?; - query.first::(conn).await + query.first(conn).await.optional() } /// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column. @@ -185,14 +186,14 @@ pub trait ApubActor { pool: &mut DbPool<'_>, actor_name: &str, include_deleted: bool, - ) -> Result + ) -> Result, Error> where Self: Sized; async fn read_from_name_and_domain( pool: &mut DbPool<'_>, actor_name: &str, protocol_domain: &str, - ) -> Result + ) -> Result, Error> where Self: Sized; } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 465ecb4cd..9c711be2a 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,11 +1,4 @@ -use crate::{ - diesel::Connection, - diesel_migrations::MigrationHarness, - newtypes::DbUrl, - CommentSortType, - SortType, -}; -use anyhow::Context; +use crate::{newtypes::DbUrl, CommentSortType, SortType}; use chrono::{DateTime, TimeDelta, Utc}; use deadpool::Runtime; use diesel::{ @@ -13,10 +6,14 @@ use diesel::{ pg::Pg, query_builder::{Query, QueryFragment}, query_dsl::methods::LimitDsl, - result::{ConnectionError, ConnectionResult, Error as DieselError, Error::QueryBuilderError}, + result::{ + ConnectionError, + ConnectionResult, + Error::{self as DieselError, QueryBuilderError}, + }, sql_types::{self, Timestamptz}, IntoSql, - PgConnection, + OptionalExtension, }; use diesel_async::{ pg::AsyncPgConnection, @@ -27,11 +24,10 @@ use diesel_async::{ }, SimpleAsyncConnection, }; -use diesel_migrations::EmbeddedMigrations; use futures_util::{future::BoxFuture, Future, FutureExt}; use i_love_jesus::CursorKey; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, settings::SETTINGS, }; use once_cell::sync::Lazy; @@ -45,7 +41,7 @@ use std::{ sync::Arc, time::{Duration, SystemTime}, }; -use tracing::{error, info}; +use tracing::error; use url::Url; const FETCH_LIMIT_DEFAULT: i64 = 10; @@ -292,9 +288,7 @@ pub fn diesel_option_overwrite(opt: Option) -> Option> { } } -pub fn diesel_option_overwrite_to_url( - opt: &Option, -) -> Result>, LemmyError> { +pub fn diesel_option_overwrite_to_url(opt: &Option) -> LemmyResult>> { match opt.as_ref().map(String::as_str) { // An empty string is an erase Some("") => Ok(Some(None)), @@ -305,9 +299,7 @@ pub fn diesel_option_overwrite_to_url( } } -pub fn diesel_option_overwrite_to_url_create( - opt: &Option, -) -> Result, LemmyError> { +pub fn diesel_option_overwrite_to_url_create(opt: &Option) -> LemmyResult> { match opt.as_ref().map(String::as_str) { // An empty string is nothing Some("") => Ok(None), @@ -363,22 +355,7 @@ impl ServerCertVerifier for NoCertVerifier { } } -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); - -fn run_migrations(db_url: &str) -> Result<(), LemmyError> { - // Needs to be a sync connection - let mut conn = PgConnection::establish(db_url).with_context(|| "Error connecting to database")?; - - info!("Running Database migrations (This may take a long time)..."); - conn - .run_pending_migrations(MIGRATIONS) - .map_err(|e| anyhow::anyhow!("Couldn't run DB Migrations: {e}"))?; - info!("Database migrations complete."); - - Ok(()) -} - -pub async fn build_db_pool() -> Result { +pub async fn build_db_pool() -> LemmyResult { let db_url = SETTINGS.get_database_url(); // We only support TLS with sslmode=require currently let tls_enabled = db_url.contains("sslmode=require"); @@ -406,7 +383,7 @@ pub async fn build_db_pool() -> Result { })) .build()?; - run_migrations(&db_url)?; + crate::schema_setup::run(&db_url)?; Ok(pool) } @@ -448,14 +425,17 @@ pub mod functions { use diesel::sql_types::{BigInt, Text, Timestamptz}; sql_function! { + #[sql_name = "r.hot_rank"] fn hot_rank(score: BigInt, time: Timestamptz) -> Double; } sql_function! { + #[sql_name = "r.scaled_rank"] fn scaled_rank(score: BigInt, time: Timestamptz, users_active_month: BigInt) -> Double; } sql_function! { + #[sql_name = "r.controversy_rank"] fn controversy_rank(upvotes: BigInt, downvotes: BigInt, score: BigInt) -> Double; } @@ -514,12 +494,12 @@ impl Queries { self, pool: &'a mut DbPool<'_>, args: Args, - ) -> Result + ) -> Result, DieselError> where RF: ReadFn<'a, T, Args>, { let conn = get_conn(pool).await?; - (self.read_fn)(conn, args).await + (self.read_fn)(conn, args).await.optional() } pub async fn list<'a, T, Args>( diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 0da5b3cff..e524e9a30 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -134,7 +134,7 @@ fn queries<'a>() -> Queries< comment_report::table.find(report_id).into_boxed(), my_person_id, ) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -191,7 +191,7 @@ impl CommentReportView { pool: &mut DbPool<'_>, report_id: CommentReportId, my_person_id: PersonId, - ) -> Result { + ) -> Result, Error> { queries().read(pool, (report_id, my_person_id)).await } @@ -396,11 +396,13 @@ mod tests { let agg = CommentAggregates::read(pool, inserted_comment.id) .await + .unwrap() .unwrap(); let read_jessica_report_view = CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) .await + .unwrap() .unwrap(); let expected_jessica_report_view = CommentReportView { comment_report: inserted_jessica_report.clone(), @@ -554,6 +556,7 @@ mod tests { let read_jessica_report_view_after_resolve = CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) .await + .unwrap() .unwrap(); let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view; diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 01ff394eb..e8957e1e9 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -191,7 +191,7 @@ fn queries<'a>() -> Queries< if my_person_id.is_none() { query = query.filter(community::visibility.eq(CommunityVisibility::Public)); } - query.first::(&mut conn).await + query.first(&mut conn).await }; let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move { @@ -375,17 +375,21 @@ impl CommentView { pool: &mut DbPool<'_>, comment_id: CommentId, my_person_id: Option, - ) -> Result { + ) -> Result, Error> { // 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 - let mut res = queries().read(pool, (comment_id, my_person_id)).await?; - if my_person_id.is_some() && res.my_vote.is_none() { - res.my_vote = Some(0); - } - if res.comment.deleted || res.comment.removed { - res.comment.content = String::new(); + if let Ok(Some(res)) = queries().read(pool, (comment_id, my_person_id)).await { + let mut new_view = res.clone(); + if my_person_id.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) } - Ok(res) } } @@ -472,7 +476,7 @@ mod tests { CommunityVisibility, SubscribedType, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use pretty_assertions::assert_eq; use serial_test::serial; @@ -700,7 +704,8 @@ mod tests { data.inserted_comment_1.id, Some(data.timmy_local_user_view.person.id), ) - .await?; + .await? + .unwrap(); // Make sure block set the creator blocked assert!(read_comment_from_blocked_person.creator_blocked); @@ -1009,7 +1014,9 @@ mod tests { } async fn expected_comment_view(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult { - let agg = CommentAggregates::read(pool, data.inserted_comment_0.id).await?; + let agg = CommentAggregates::read(pool, data.inserted_comment_0.id) + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; Ok(CommentView { creator_banned_from_community: false, banned_from_community: false, @@ -1154,8 +1161,8 @@ mod tests { .await?; assert_eq!(5, authenticated_query.len()); - let unauthenticated_comment = CommentView::read(pool, data.inserted_comment_0.id, None).await; - assert!(unauthenticated_comment.is_err()); + let unauthenticated_comment = CommentView::read(pool, data.inserted_comment_0.id, None).await?; + assert!(unauthenticated_comment.is_none()); let authenticated_comment = CommentView::read( pool, @@ -1202,7 +1209,8 @@ mod tests { data.inserted_comment_0.id, Some(inserted_banned_from_comm_local_user.person_id), ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; assert!(comment_view.banned_from_community); @@ -1222,7 +1230,8 @@ mod tests { data.inserted_comment_0.id, Some(data.timmy_local_user_view.person.id), ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindComment)?; assert!(!comment_view.banned_from_community); diff --git a/crates/db_views/src/lib.rs b/crates/db_views/src/lib.rs index 73310d743..e93c7409d 100644 --- a/crates/db_views/src/lib.rs +++ b/crates/db_views/src/lib.rs @@ -8,6 +8,8 @@ pub mod comment_view; #[cfg(feature = "full")] pub mod custom_emoji_view; #[cfg(feature = "full")] +pub mod local_image_view; +#[cfg(feature = "full")] pub mod local_user_view; #[cfg(feature = "full")] pub mod post_report_view; diff --git a/crates/db_views/src/local_image_view.rs b/crates/db_views/src/local_image_view.rs new file mode 100644 index 000000000..7b5b97095 --- /dev/null +++ b/crates/db_views/src/local_image_view.rs @@ -0,0 +1,61 @@ +use crate::structs::LocalImageView; +use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel_async::RunQueryDsl; +use lemmy_db_schema::{ + newtypes::LocalUserId, + schema::{local_image, local_user, person}, + utils::{get_conn, limit_and_offset, DbPool}, +}; + +impl LocalImageView { + async fn get_all_helper( + pool: &mut DbPool<'_>, + user_id: Option, + page: Option, + limit: Option, + ignore_page_limits: bool, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let mut query = local_image::table + .inner_join(local_user::table) + .inner_join(person::table.on(local_user::person_id.eq(person::id))) + .select((local_image::all_columns, person::all_columns)) + .order_by(local_image::published.desc()) + .into_boxed(); + + if let Some(user_id) = user_id { + query = query.filter(local_image::local_user_id.eq(user_id)) + } + + if !ignore_page_limits { + let (limit, offset) = limit_and_offset(page, limit)?; + query = query.limit(limit).offset(offset); + } + + query.load::(conn).await + } + + pub async fn get_all_paged_by_local_user_id( + pool: &mut DbPool<'_>, + user_id: LocalUserId, + page: Option, + limit: Option, + ) -> Result, Error> { + Self::get_all_helper(pool, Some(user_id), page, limit, false).await + } + + pub async fn get_all_by_local_user_id( + pool: &mut DbPool<'_>, + user_id: LocalUserId, + ) -> Result, Error> { + Self::get_all_helper(pool, Some(user_id), None, None, true).await + } + + pub async fn get_all( + pool: &mut DbPool<'_>, + page: Option, + limit: Option, + ) -> Result, Error> { + Self::get_all_helper(pool, None, page, limit, false).await + } +} diff --git a/crates/db_views/src/local_user_view.rs b/crates/db_views/src/local_user_view.rs index 96d6a3530..0c13b0a68 100644 --- a/crates/db_views/src/local_user_view.rs +++ b/crates/db_views/src/local_user_view.rs @@ -62,7 +62,7 @@ fn queries<'a>( .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) + .first(&mut conn) .await }; @@ -86,28 +86,37 @@ fn queries<'a>( } impl LocalUserView { - pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result { + pub async fn read( + pool: &mut DbPool<'_>, + local_user_id: LocalUserId, + ) -> Result, Error> { queries().read(pool, ReadBy::Id(local_user_id)).await } - pub async fn read_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + pub async fn read_person( + pool: &mut DbPool<'_>, + person_id: PersonId, + ) -> Result, Error> { queries().read(pool, ReadBy::Person(person_id)).await } - pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result { + pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result, Error> { queries().read(pool, ReadBy::Name(name)).await } pub async fn find_by_email_or_name( pool: &mut DbPool<'_>, name_or_email: &str, - ) -> Result { + ) -> Result, Error> { queries() .read(pool, ReadBy::NameOrEmail(name_or_email)) .await } - pub async fn find_by_email(pool: &mut DbPool<'_>, from_email: &str) -> Result { + pub async fn find_by_email( + pool: &mut DbPool<'_>, + from_email: &str, + ) -> Result, Error> { queries().read(pool, ReadBy::Email(from_email)).await } diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 878937d01..76679df1e 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -163,7 +163,7 @@ fn queries<'a>() -> Queries< post_report::table.find(report_id).into_boxed(), my_person_id, ) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -219,7 +219,7 @@ impl PostReportView { pool: &mut DbPool<'_>, report_id: PostReportId, my_person_id: PersonId, - ) -> Result { + ) -> Result, Error> { queries().read(pool, (report_id, my_person_id)).await } @@ -421,6 +421,7 @@ mod tests { let read_jessica_report_view = PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) .await + .unwrap() .unwrap(); assert_eq!( @@ -458,6 +459,7 @@ mod tests { let read_jessica_report_view_after_resolve = PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id) .await + .unwrap() .unwrap(); assert!(read_jessica_report_view_after_resolve.post_report.resolved); assert_eq!( diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index b323437d5..257bcc76c 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -305,7 +305,7 @@ fn queries<'a>() -> Queries< Commented::new(query) .text("PostView::read") - .first::(&mut conn) + .first(&mut conn) .await }; @@ -581,12 +581,10 @@ impl PostView { post_id: PostId, my_person_id: Option, is_mod_or_admin: bool, - ) -> Result { - let res = queries() + ) -> Result, Error> { + queries() .read(pool, (post_id, my_person_id, is_mod_or_admin)) - .await?; - - Ok(res) + .await } } @@ -597,19 +595,21 @@ impl PaginationCursor { PaginationCursor(format!("P{:x}", view.counts.post_id.0)) } pub async fn read(&self, pool: &mut DbPool<'_>) -> Result { - Ok(PaginationCursorData( - PostAggregates::read( - pool, - PostId( - self - .0 - .get(1..) - .and_then(|e| i32::from_str_radix(e, 16).ok()) - .ok_or_else(|| Error::QueryBuilderError("Could not parse pagination token".into()))?, - ), - ) - .await?, - )) + let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into()); + let token = PostAggregates::read( + pool, + PostId( + self + .0 + .get(1..) + .and_then(|e| i32::from_str_radix(e, 16).ok()) + .ok_or_else(err_msg)?, + ), + ) + .await? + .ok_or_else(err_msg)?; + + Ok(PaginationCursorData(token)) } } @@ -646,7 +646,7 @@ impl<'a> PostQuery<'a> { site: &Site, pool: &mut DbPool<'_>, ) -> Result>, Error> { - // first get one page for the most popular community to get an upper bound for the the page end for the real query + // first get one page for the most popular community to get an upper bound for the page end for the real query // the reason this is needed is that when fetching posts for a single community PostgreSQL can optimize // the query to use an index on e.g. (=, >=, >=, >=) and fetch only LIMIT rows // but for the followed-communities query it has to query the index on (IN, >=, >=, >=) @@ -783,7 +783,7 @@ mod tests { SortType, SubscribedType, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use pretty_assertions::assert_eq; use serial_test::serial; use std::{collections::HashSet, time::Duration}; @@ -964,7 +964,8 @@ mod tests { Some(data.local_user_view.person.id), false, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; let expected_post_listing_with_user = expected_post_view(&data, pool).await?; @@ -1014,7 +1015,9 @@ mod tests { .await?; let read_post_listing_single_no_person = - PostView::read(pool, data.inserted_post.id, None, false).await?; + PostView::read(pool, data.inserted_post.id, None, false) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; let expected_post_listing_no_person = expected_post_view(&data, pool).await?; @@ -1091,7 +1094,8 @@ mod tests { Some(data.local_user_view.person.id), false, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; let mut expected_post_with_upvote = expected_post_view(&data, pool).await?; expected_post_with_upvote.my_vote = Some(1); @@ -1573,7 +1577,9 @@ mod tests { &data.inserted_community, &data.inserted_post, ); - let agg = PostAggregates::read(pool, inserted_post.id).await?; + let agg = PostAggregates::read(pool, inserted_post.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; Ok(PostView { post: Post { @@ -1716,8 +1722,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_err()); + let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await?; + assert!(unauthenticated_post.is_none()); let authenticated_post = PostView::read( pool, @@ -1767,7 +1773,8 @@ mod tests { Some(inserted_banned_from_comm_local_user.person_id), false, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; assert!(post_view.banned_from_community); @@ -1788,7 +1795,8 @@ mod tests { Some(data.local_user_view.person.id), false, ) - .await?; + .await? + .ok_or(LemmyErrorType::CouldntFindPost)?; 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 dff9820d9..a402d0d4f 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -42,7 +42,7 @@ fn queries<'a>() -> Queries< let read = move |mut conn: DbConn<'a>, report_id: PrivateMessageReportId| async move { all_joins(private_message_report::table.find(report_id).into_boxed()) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -77,7 +77,7 @@ impl PrivateMessageReportView { pub async fn read( pool: &mut DbPool<'_>, report_id: PrivateMessageReportId, - ) -> Result { + ) -> Result, Error> { queries().read(pool, report_id).await } diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index 466f3bdf0..764ef1dcb 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -53,7 +53,7 @@ fn queries<'a>() -> Queries< all_joins(private_message::table.find(private_message_id).into_boxed()) .order_by(private_message::published.desc()) .select(selection) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -113,7 +113,7 @@ impl PrivateMessageView { pub async fn read( pool: &mut DbPool<'_>, private_message_id: PrivateMessageId, - ) -> Result { + ) -> Result, Error> { queries().read(pool, private_message_id).await } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 85916bb90..7346dcd0d 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -42,7 +42,7 @@ fn queries<'a>() -> Queries< .find(registration_application_id) .into_boxed(), ) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -76,7 +76,7 @@ impl RegistrationApplicationView { pub async fn read( pool: &mut DbPool<'_>, registration_application_id: i32, - ) -> Result { + ) -> Result, Error> { queries().read(pool, registration_application_id).await } @@ -209,6 +209,7 @@ mod tests { let read_sara_app_view = RegistrationApplicationView::read(pool, sara_app.id) .await + .unwrap() .unwrap(); let jess_person_form = PersonInsertForm::builder() @@ -240,6 +241,7 @@ mod tests { let read_jess_app_view = RegistrationApplicationView::read(pool, jess_app.id) .await + .unwrap() .unwrap(); let mut expected_sara_app_view = RegistrationApplicationView { @@ -343,6 +345,7 @@ mod tests { let read_sara_app_view_after_approve = RegistrationApplicationView::read(pool, sara_app.id) .await + .unwrap() .unwrap(); // Make sure the columns changed diff --git a/crates/db_views/src/site_view.rs b/crates/db_views/src/site_view.rs index 52074c982..8f0722318 100644 --- a/crates/db_views/src/site_view.rs +++ b/crates/db_views/src/site_view.rs @@ -1,5 +1,5 @@ use crate::structs::SiteView; -use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{result::Error, ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ schema::{local_site, local_site_rate_limit, site, site_aggregates}, @@ -7,7 +7,7 @@ use lemmy_db_schema::{ }; impl SiteView { - pub async fn read_local(pool: &mut DbPool<'_>) -> Result { + pub async fn read_local(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; site::table .inner_join(local_site::table) @@ -21,7 +21,8 @@ impl SiteView { local_site_rate_limit::all_columns, site_aggregates::all_columns, )) - .first::(conn) + .first(conn) .await + .optional() } } diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index a290ca4a1..4311710ab 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -8,6 +8,7 @@ use lemmy_db_schema::{ community::Community, custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword, + images::LocalImage, local_site::LocalSite, local_site_rate_limit::LocalSiteRateLimit, local_user::LocalUser, @@ -214,3 +215,13 @@ pub struct VoteView { pub creator_banned_from_community: bool, pub score: i16, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +/// A local image view. +pub struct LocalImageView { + pub local_image: LocalImage, + pub person: Person, +} diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 77f744a97..baa4f5601 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -188,7 +188,7 @@ fn queries<'a>() -> Queries< comment_reply::table.find(comment_reply_id).into_boxed(), my_person_id, ) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -234,7 +234,7 @@ impl CommentReplyView { pool: &mut DbPool<'_>, comment_reply_id: CommentReplyId, my_person_id: Option, - ) -> Result { + ) -> Result, Error> { queries().read(pool, (comment_reply_id, my_person_id)).await } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 3107478dc..b5c23c6ef 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -102,7 +102,7 @@ fn queries<'a>() -> Queries< query = query.filter(community::visibility.eq(CommunityVisibility::Public)); } - query.first::(&mut conn).await + query.first(&mut conn).await }; let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move { @@ -194,7 +194,7 @@ impl CommunityView { community_id: CommunityId, my_person_id: Option, is_mod_or_admin: bool, - ) -> Result { + ) -> Result, Error> { queries() .read(pool, (community_id, my_person_id, is_mod_or_admin)) .await @@ -209,9 +209,10 @@ impl CommunityView { 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) } else { - let is_admin = PersonView::read(pool, person_id).await?.is_admin; - Ok(is_admin) + Ok(false) } } @@ -223,11 +224,12 @@ impl CommunityView { let is_mod_of_any = CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await?; if is_mod_of_any { - return Ok(true); + Ok(true) + } else if let Ok(Some(person_view)) = PersonView::read(pool, person_id).await { + Ok(person_view.is_admin) + } else { + Ok(false) } - - let is_admin = PersonView::read(pool, person_id).await?.is_admin; - Ok(is_admin) } } @@ -384,8 +386,10 @@ mod tests { assert_eq!(1, authenticated_query.len()); let unauthenticated_community = - CommunityView::read(pool, data.inserted_community.id, None, false).await; - assert!(unauthenticated_community.is_err()); + CommunityView::read(pool, data.inserted_community.id, None, false) + .await + .unwrap(); + assert!(unauthenticated_community.is_none()); let authenticated_community = CommunityView::read( pool, diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 399850292..65c0bd0e6 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -187,7 +187,7 @@ fn queries<'a>() -> Queries< person_mention::table.find(person_mention_id).into_boxed(), my_person_id, ) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -233,7 +233,7 @@ impl PersonMentionView { pool: &mut DbPool<'_>, person_mention_id: PersonMentionId, my_person_id: Option, - ) -> Result { + ) -> Result, Error> { queries() .read(pool, (person_mention_id, my_person_id)) .await diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index b3eac296d..5734bc812 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -72,7 +72,7 @@ fn queries<'a>( let read = move |mut conn: DbConn<'a>, person_id: PersonId| async move { all_joins(person::table.find(person_id).into_boxed()) - .first::(&mut conn) + .first(&mut conn) .await }; @@ -134,7 +134,7 @@ fn queries<'a>( } impl PersonView { - pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { + pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { queries().read(pool, person_id).await } @@ -163,12 +163,10 @@ impl PersonQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] #[allow(clippy::indexing_slicing)] mod tests { use super::*; - use diesel::NotFound; use lemmy_db_schema::{ assert_length, source::{ @@ -179,7 +177,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use pretty_assertions::assert_eq; use serial_test::serial; @@ -254,8 +252,8 @@ mod tests { ) .await?; - let read = PersonView::read(pool, data.alice.id).await; - assert_eq!(read.err(), Some(NotFound)); + let read = PersonView::read(pool, data.alice.id).await?; + assert!(read.is_none()); let list = PersonQuery { sort: Some(SortType::New), @@ -314,10 +312,16 @@ 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?.is_admin; + let is_admin = PersonView::read(pool, data.alice.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? + .is_admin; assert!(is_admin); - let is_admin = PersonView::read(pool, data.bob.id).await?.is_admin; + let is_admin = PersonView::read(pool, data.bob.id) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)? + .is_admin; assert!(!is_admin); cleanup(data, pool).await diff --git a/crates/federate/src/util.rs b/crates/federate/src/util.rs index 2809b9bb4..b057ab0fe 100644 --- a/crates/federate/src/util.rs +++ b/crates/federate/src/util.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use diesel::prelude::*; use diesel_async::RunQueryDsl; -use lemmy_api_common::lemmy_utils::CACHE_DURATION_SHORT; +use lemmy_api_common::lemmy_utils::CACHE_DURATION_FEDERATION; use lemmy_apub::{ activity_lists::SharedInboxActivities, fetcher::{site_or_community_or_user::SiteOrCommunityOrUser, user_or_community::UserOrCommunity}, @@ -151,7 +151,6 @@ pub(crate) async fn get_activity_cached( .try_get_with(activity_id, async { let row = SentActivity::read(pool, activity_id) .await - .optional() .context("could not read activity")?; let Some(mut row) = row else { return anyhow::Result::<_, anyhow::Error>::Ok(None); @@ -169,8 +168,11 @@ pub(crate) async fn get_activity_cached( /// return the most current activity id (with 1 second cache) pub(crate) async fn get_latest_activity_id(pool: &mut DbPool<'_>) -> Result { - static CACHE: Lazy> = - Lazy::new(|| Cache::builder().time_to_live(CACHE_DURATION_SHORT).build()); + static CACHE: Lazy> = Lazy::new(|| { + Cache::builder() + .time_to_live(CACHE_DURATION_FEDERATION) + .build() + }); CACHE .try_get_with((), async { use diesel::dsl::max; diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index b8ca2d5a6..5e3db357a 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -22,7 +22,7 @@ use lemmy_db_views_actor::{ }; use lemmy_utils::{ cache_header::cache_1hour, - error::{LemmyError, LemmyErrorType}, + error::{LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::{markdown_to_html, sanitize_html}, }; use once_cell::sync::Lazy; @@ -151,8 +151,10 @@ async fn get_feed_data( sort_type: SortType, limit: i64, page: i64, -) -> Result { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; check_private_instance(&None, &site_view.local_site)?; @@ -256,9 +258,13 @@ async fn get_feed_user( limit: &i64, page: &i64, user_name: &str, -) -> Result { - let site_view = SiteView::read_local(&mut context.pool()).await?; - let person = Person::read_from_name(&mut context.pool(), user_name, false).await?; +) -> LemmyResult { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let person = Person::read_from_name(&mut context.pool(), user_name, false) + .await? + .ok_or(LemmyErrorType::CouldntFindPerson)?; check_private_instance(&None, &site_view.local_site)?; @@ -292,9 +298,13 @@ async fn get_feed_community( limit: &i64, page: &i64, community_name: &str, -) -> Result { - let site_view = SiteView::read_local(&mut context.pool()).await?; - let community = Community::read_from_name(&mut context.pool(), community_name, false).await?; +) -> LemmyResult { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; + let community = Community::read_from_name(&mut context.pool(), community_name, false) + .await? + .ok_or(LemmyErrorType::CouldntFindCommunity)?; if community.visibility != CommunityVisibility::Public { return Err(LemmyErrorType::CouldntFindCommunity.into()); } @@ -335,8 +345,10 @@ async fn get_feed_front( limit: &i64, page: &i64, jwt: &str, -) -> Result { - let site_view = SiteView::read_local(&mut context.pool()).await?; +) -> LemmyResult { + let site_view = SiteView::read_local(&mut context.pool()) + .await? + .ok_or(LemmyErrorType::LocalSiteNotSetup)?; let local_user = local_user_view_from_jwt(jwt, context).await?; check_private_instance(&Some(local_user.clone()), &site_view.local_site)?; @@ -370,8 +382,10 @@ async fn get_feed_front( } #[tracing::instrument(skip_all)] -async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> Result { - let site_view = SiteView::read_local(&mut context.pool()).await?; +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 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; @@ -425,7 +439,7 @@ fn create_reply_and_mention_items( replies: Vec, mentions: Vec, protocol_and_hostname: &str, -) -> Result, LemmyError> { +) -> LemmyResult> { let mut reply_items: Vec = replies .iter() .map(|r| { @@ -438,7 +452,7 @@ fn create_reply_and_mention_items( protocol_and_hostname, ) }) - .collect::, LemmyError>>()?; + .collect::>>()?; let mut mention_items: Vec = mentions .iter() @@ -452,7 +466,7 @@ fn create_reply_and_mention_items( protocol_and_hostname, ) }) - .collect::, LemmyError>>()?; + .collect::>>()?; reply_items.append(&mut mention_items); Ok(reply_items) @@ -465,7 +479,7 @@ fn build_item( url: &str, content: &str, protocol_and_hostname: &str, -) -> Result { +) -> LemmyResult { // TODO add images let author_url = format!("{protocol_and_hostname}/u/{creator_name}"); let guid = Some(Guid { @@ -489,10 +503,7 @@ fn build_item( } #[tracing::instrument(skip_all)] -fn create_post_items( - posts: Vec, - protocol_and_hostname: &str, -) -> Result, LemmyError> { +fn create_post_items(posts: Vec, protocol_and_hostname: &str) -> LemmyResult> { let mut items: Vec = Vec::new(); for p in posts { diff --git a/crates/routes/src/images.rs b/crates/routes/src/images.rs index 58ca9c8ef..671aa223e 100644 --- a/crates/routes/src/images.rs +++ b/crates/routes/src/images.rs @@ -66,7 +66,7 @@ fn adapt_request( client: &ClientWithMiddleware, url: String, ) -> RequestBuilder { - // remove accept-encoding header so that pictrs doesnt compress the response + // remove accept-encoding header so that pictrs doesn't compress the response const INVALID_HEADERS: &[HeaderName] = &[ACCEPT_ENCODING, HOST]; let client_request = client diff --git a/crates/routes/src/lib.rs b/crates/routes/src/lib.rs index ec28fda45..4f8d60246 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::LemmyError; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; pub mod feeds; pub mod images; @@ -8,12 +8,11 @@ pub mod nodeinfo; pub mod webfinger; #[tracing::instrument(skip_all)] -async fn local_user_view_from_jwt( - jwt: &str, - context: &LemmyContext, -) -> Result { +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?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) + .await? + .ok_or(LemmyErrorType::CouldntFindLocalUser)?; 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 62082b4c8..dbb2ef68a 100644 --- a/crates/routes/src/nodeinfo.rs +++ b/crates/routes/src/nodeinfo.rs @@ -5,7 +5,7 @@ use lemmy_db_schema::RegistrationMode; use lemmy_db_views::structs::SiteView; use lemmy_utils::{ cache_header::{cache_1hour, cache_3days}, - error::LemmyError, + error::{LemmyError, LemmyResult}, VERSION, }; use serde::{Deserialize, Serialize}; @@ -24,9 +24,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { ); } -async fn node_info_well_known( - context: web::Data, -) -> Result { +async fn node_info_well_known(context: web::Data) -> LemmyResult { let node_info = NodeInfoWellKnown { links: vec![NodeInfoWellKnownLinks { rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, @@ -42,7 +40,8 @@ async fn node_info_well_known( async fn node_info(context: web::Data) -> Result { let site_view = SiteView::read_local(&mut context.pool()) .await - .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?; + .map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))? + .ok_or(ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?; let protocols = if site_view.local_site.federation_enabled { Some(vec!["activitypub".to_string()]) diff --git a/crates/routes/src/webfinger.rs b/crates/routes/src/webfinger.rs index 95092b244..f2a67c0fc 100644 --- a/crates/routes/src/webfinger.rs +++ b/crates/routes/src/webfinger.rs @@ -9,7 +9,7 @@ use lemmy_db_schema::{ traits::ApubActor, CommunityVisibility, }; -use lemmy_utils::{cache_header::cache_3days, error::LemmyError}; +use lemmy_utils::{cache_header::cache_3days, error::LemmyResult}; use serde::Deserialize; use std::collections::HashMap; use url::Url; @@ -35,7 +35,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { async fn get_webfinger_response( info: Query, context: Data, -) -> Result { +) -> LemmyResult { let name = extract_webfinger_name(&info.resource, &context)?; let links = if name == context.settings().hostname { @@ -47,10 +47,12 @@ async fn get_webfinger_response( let user_id: Option = Person::read_from_name(&mut context.pool(), name, false) .await .ok() + .flatten() .map(|c| c.actor_id.into()); let community_id: Option = Community::read_from_name(&mut context.pool(), name, false) .await .ok() + .flatten() .and_then(|c| { if c.visibility == CommunityVisibility::Public { let id: Url = c.actor_id.into(); @@ -61,7 +63,7 @@ async fn get_webfinger_response( }); // Mastodon seems to prioritize the last webfinger item in case of duplicates. Put - // community last so that it gets prioritized. For Lemmy the order doesnt matter. + // community last so that it gets prioritized. For Lemmy the order doesn't matter. vec![ webfinger_link_for_actor(user_id, "Person", &context), webfinger_link_for_actor(community_id, "Group", &context), diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 0a1b22c32..44c469ab1 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -78,7 +78,7 @@ openssl = { version = "0.10.64", optional = true } html2text = { version = "0.6.0", optional = true } deser-hjson = { version = "2.2.4", optional = true } smart-default = { version = "0.7.1", optional = true } -lettre = { version = "0.11.4", features = [ +lettre = { version = "0.11.6", features = [ "tokio1", "tokio1-native-tls", ], optional = true } diff --git a/crates/utils/src/email.rs b/crates/utils/src/email.rs index 1a786b0ef..7bac7ad67 100644 --- a/crates/utils/src/email.rs +++ b/crates/utils/src/email.rs @@ -1,5 +1,5 @@ use crate::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorType}, + error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, settings::structs::Settings, }; use html2text; @@ -25,7 +25,7 @@ pub async fn send_email( to_username: &str, html: &str, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let email_config = settings.email.clone().ok_or(LemmyErrorType::NoEmailSetup)?; let domain = settings.hostname.clone(); diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index d25845894..a687f5136 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -43,6 +43,16 @@ pub enum LemmyErrorType { BannedFromCommunity, CouldntFindCommunity, CouldntFindPerson, + CouldntFindComment, + CouldntFindCommentReport, + CouldntFindPostReport, + CouldntFindPrivateMessageReport, + CouldntFindLocalUser, + CouldntFindPersonMention, + CouldntFindRegistrationApplication, + CouldntFindCommentReply, + CouldntFindPrivateMessage, + CouldntFindActivity, PersonIsBlocked, CommunityIsBlocked, InstanceIsBlocked, @@ -92,6 +102,7 @@ pub enum LemmyErrorType { PageDoesNotSpecifyGroup, NoCommunityFoundInCc, NoEmailSetup, + LocalSiteNotSetup, EmailSmtpServerNeedsAPort, MissingAnEmail, RateLimitError, @@ -244,11 +255,11 @@ cfg_if! { } pub trait LemmyErrorExt> { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; + fn with_lemmy_type(self, error_type: LemmyErrorType) -> LemmyResult; } impl> LemmyErrorExt for Result { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> LemmyResult { self.map_err(|error| LemmyError { error_type, inner: error.into(), @@ -257,12 +268,12 @@ cfg_if! { } } pub trait LemmyErrorExt2 { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result; + fn with_lemmy_type(self, error_type: LemmyErrorType) -> LemmyResult; fn into_anyhow(self) -> Result; } - impl LemmyErrorExt2 for Result { - fn with_lemmy_type(self, error_type: LemmyErrorType) -> Result { + impl LemmyErrorExt2 for LemmyResult { + fn with_lemmy_type(self, error_type: LemmyErrorType) -> LemmyResult { self.map_err(|mut e| { e.error_type = error_type; e diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 46508eb50..95c1d0144 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -24,9 +24,11 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const REQWEST_TIMEOUT: Duration = Duration::from_secs(10); #[cfg(debug_assertions)] -pub const CACHE_DURATION_SHORT: Duration = Duration::from_millis(500); +pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_millis(500); #[cfg(not(debug_assertions))] -pub const CACHE_DURATION_SHORT: Duration = Duration::from_secs(60); +pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_secs(60); + +pub const CACHE_DURATION_API: Duration = Duration::from_secs(1); #[macro_export] macro_rules! location_info { diff --git a/crates/utils/src/settings/mod.rs b/crates/utils/src/settings/mod.rs index f630d0217..6efa3fdd3 100644 --- a/crates/utils/src/settings/mod.rs +++ b/crates/utils/src/settings/mod.rs @@ -1,4 +1,4 @@ -use crate::{error::LemmyError, location_info}; +use crate::{error::LemmyResult, location_info}; use anyhow::{anyhow, Context}; use deser_hjson::from_str; use once_cell::sync::Lazy; @@ -13,8 +13,17 @@ use structs::{DatabaseConnection, PictrsConfig, PictrsImageMode, Settings}; static DEFAULT_CONFIG_FILE: &str = "config/config.hjson"; pub static SETTINGS: Lazy = Lazy::new(|| { - Settings::init().expect("Failed to load settings file, see documentation (https://join-lemmy.org/docs/en/administration/configuration.html)") + if env::var("LEMMY_INITIALIZE_WITH_DEFAULT_SETTINGS").is_ok() { + println!( + "LEMMY_INITIALIZE_WITH_DEFAULT_SETTINGS was set, any configuration file has been ignored." + ); + println!("Use with other environment variables to configure this instance further; e.g. LEMMY_DATABASE_URL."); + Settings::default() + } else { + Settings::init().expect("Failed to load settings file, see documentation (https://join-lemmy.org/docs/en/administration/configuration.html).") + } }); + static WEBFINGER_REGEX: Lazy = Lazy::new(|| { Regex::new(&format!( "^acct:([a-zA-Z0-9_]{{3,}})@{}$", @@ -29,10 +38,8 @@ impl Settings { /// Note: The env var `LEMMY_DATABASE_URL` is parsed in /// `lemmy_db_schema/src/lib.rs::get_database_url_from_env()` /// Warning: Only call this once. - pub(crate) fn init() -> Result { - // Read the config file + pub(crate) fn init() -> LemmyResult { let config = from_str::(&Self::read_config_file()?)?; - if config.hostname == "unset" { Err(anyhow!("Hostname variable is not set!").into()) } else { @@ -101,7 +108,7 @@ impl Settings { WEBFINGER_REGEX.clone() } - pub fn pictrs_config(&self) -> Result { + pub fn pictrs_config(&self) -> LemmyResult { self .pictrs .clone() diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 4a8d8afb6..91a5b37f4 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -97,7 +97,7 @@ pub struct PictrsConfig { #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document, PartialEq)] #[serde(deny_unknown_fields)] pub enum PictrsImageMode { - /// Leave images unchanged, don't generate any local thumbnails for post urls. Instead the the + /// Leave images unchanged, don't generate any local thumbnails for post urls. Instead the /// Opengraph image is directly returned as thumbnail None, /// Generate thumbnails for external post urls and store them persistently in pict-rs. This diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index e379ae439..c7c8a88e3 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -1,4 +1,4 @@ -use crate::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; +use crate::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use regex::{Regex, RegexBuilder}; pub fn remove_slurs(test: &str, slur_regex: &Option) -> String { @@ -39,7 +39,7 @@ pub fn build_slur_regex(regex_str: Option<&str>) -> Option { }) } -pub fn check_slurs(text: &str, slur_regex: &Option) -> Result<(), LemmyError> { +pub fn check_slurs(text: &str, slur_regex: &Option) -> LemmyResult<()> { if let Err(slurs) = slur_check(text, slur_regex) { Err(anyhow::anyhow!("{}", slurs_vec_to_str(&slurs))).with_lemmy_type(LemmyErrorType::Slurs) } else { @@ -47,10 +47,7 @@ pub fn check_slurs(text: &str, slur_regex: &Option) -> Result<(), LemmyEr } } -pub fn check_slurs_opt( - text: &Option, - slur_regex: &Option, -) -> Result<(), LemmyError> { +pub fn check_slurs_opt(text: &Option, slur_regex: &Option) -> LemmyResult<()> { match text { Some(t) => check_slurs(t, slur_regex), None => Ok(()), diff --git a/crates/utils/translations b/crates/utils/translations index b3131d688..c88dd1e3b 160000 --- a/crates/utils/translations +++ b/crates/utils/translations @@ -1 +1 @@ -Subproject commit b3131d6881adb639dc0e298cc7c213c5245091f6 +Subproject commit c88dd1e3b36ee1617f1b86acf94c1b7946e97cd4 diff --git a/docker/Dockerfile b/docker/Dockerfile index ff3dfc7ff..25761d3c1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,11 +1,11 @@ -# syntax=docker/dockerfile:1.6 +# syntax=docker/dockerfile:1.7 ARG RUST_VERSION=1.77 ARG CARGO_BUILD_FEATURES=default ARG RUST_RELEASE_MODE=debug ARG AMD_BUILDER_IMAGE=rust:${RUST_VERSION} # Repo: https://github.com/raskyld/lemmy-cross-toolchains -ARG ARM_BUILDER_IMAGE="ghcr.io/raskyld/aarch64-lemmy-linux-gnu:v0.2.0" +ARG ARM_BUILDER_IMAGE="ghcr.io/raskyld/aarch64-lemmy-linux-gnu:v0.3.0" ARG AMD_RUNNER_IMAGE=debian:bookworm-slim ARG ARM_RUNNER_IMAGE=debian:bookworm-slim diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1bf38863f..eeeb9e24e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -117,7 +117,7 @@ services: "track_activity_query_size=1048576", ] ports: - # use a different port so it doesnt conflict with potential postgres db running on the host + # use a different port so it doesn't conflict with potential postgres db running on the host - "5433:5432" environment: - POSTGRES_USER=lemmy diff --git a/migrations/2024-02-24-034523_replaceable-schema/down.sql b/migrations/2024-02-24-034523_replaceable-schema/down.sql new file mode 100644 index 000000000..db8feacdf --- /dev/null +++ b/migrations/2024-02-24-034523_replaceable-schema/down.sql @@ -0,0 +1,1062 @@ +DROP SCHEMA IF EXISTS r CASCADE; + +DROP INDEX idx_site_aggregates_1_row_only; + +CREATE FUNCTION comment_aggregates_comment () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + INSERT INTO comment_aggregates (comment_id, published) + VALUES (NEW.id, NEW.published); + ELSIF (TG_OP = 'DELETE') THEN + DELETE FROM comment_aggregates + WHERE comment_id = OLD.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION comment_aggregates_score () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + UPDATE + comment_aggregates ca + SET + score = score + NEW.score, + upvotes = CASE WHEN NEW.score = 1 THEN + upvotes + 1 + ELSE + upvotes + END, + downvotes = CASE WHEN NEW.score = - 1 THEN + downvotes + 1 + ELSE + downvotes + END, + controversy_rank = controversy_rank (ca.upvotes + CASE WHEN NEW.score = 1 THEN + 1 + ELSE + 0 + END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN + 1 + ELSE + 0 + END::numeric) + WHERE + ca.comment_id = NEW.comment_id; + ELSIF (TG_OP = 'DELETE') THEN + -- Join to comment because that comment may not exist anymore + UPDATE + comment_aggregates ca + SET + score = score - OLD.score, + upvotes = CASE WHEN OLD.score = 1 THEN + upvotes - 1 + ELSE + upvotes + END, + downvotes = CASE WHEN OLD.score = - 1 THEN + downvotes - 1 + ELSE + downvotes + END, + controversy_rank = controversy_rank (ca.upvotes + CASE WHEN NEW.score = 1 THEN + 1 + ELSE + 0 + END::numeric, ca.downvotes + CASE WHEN NEW.score = - 1 THEN + 1 + ELSE + 0 + END::numeric) + FROM + comment c + WHERE + ca.comment_id = c.id + AND ca.comment_id = OLD.comment_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION community_aggregates_comment_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + community_aggregates ca + SET + comments = comments + 1 + FROM + post p + WHERE + p.id = NEW.post_id + AND ca.community_id = p.community_id; + ELSIF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + community_aggregates ca + SET + comments = comments - 1 + FROM + post p + WHERE + p.id = OLD.post_id + AND ca.community_id = p.community_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION community_aggregates_community () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + INSERT INTO community_aggregates (community_id, published) + VALUES (NEW.id, NEW.published); + ELSIF (TG_OP = 'DELETE') THEN + DELETE FROM community_aggregates + WHERE community_id = OLD.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION community_aggregates_post_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + community_aggregates + SET + posts = posts + 1 + WHERE + community_id = NEW.community_id; + IF (TG_OP = 'UPDATE') THEN + -- Post was restored, so restore comment counts as well + UPDATE + community_aggregates ca + SET + posts = coalesce(cd.posts, 0), + comments = coalesce(cd.comments, 0) + FROM ( + SELECT + c.id, + count(DISTINCT p.id) AS posts, + count(DISTINCT ct.id) AS comments + FROM + community c + LEFT JOIN post p ON c.id = p.community_id + AND p.deleted = 'f' + AND p.removed = 'f' + LEFT JOIN comment ct ON p.id = ct.post_id + AND ct.deleted = 'f' + AND ct.removed = 'f' + WHERE + c.id = NEW.community_id + GROUP BY + c.id) cd + WHERE + ca.community_id = NEW.community_id; + END IF; + ELSIF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + community_aggregates + SET + posts = posts - 1 + WHERE + community_id = OLD.community_id; + -- Update the counts if the post got deleted + UPDATE + community_aggregates ca + SET + posts = coalesce(cd.posts, 0), + comments = coalesce(cd.comments, 0) + FROM ( + SELECT + c.id, + count(DISTINCT p.id) AS posts, + count(DISTINCT ct.id) AS comments + FROM + community c + LEFT JOIN post p ON c.id = p.community_id + AND p.deleted = 'f' + AND p.removed = 'f' + LEFT JOIN comment ct ON p.id = ct.post_id + AND ct.deleted = 'f' + AND ct.removed = 'f' + WHERE + c.id = OLD.community_id + GROUP BY + c.id) cd + WHERE + ca.community_id = OLD.community_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION community_aggregates_post_count_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + community_aggregates + SET + posts = posts + post_group.count + FROM ( + SELECT + community_id, + count(*) + FROM + new_post + GROUP BY + community_id) post_group +WHERE + community_aggregates.community_id = post_group.community_id; + RETURN NULL; +END +$$; + +CREATE FUNCTION community_aggregates_subscriber_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + UPDATE + community_aggregates ca + SET + subscribers = subscribers + community.local::int, + subscribers_local = subscribers_local + person.local::int + FROM + community + LEFT JOIN person ON person.id = NEW.person_id + WHERE + community.id = NEW.community_id + AND community.id = ca.community_id + AND person.local IS NOT NULL; + ELSIF (TG_OP = 'DELETE') THEN + UPDATE + community_aggregates ca + SET + subscribers = subscribers - community.local::int, + subscribers_local = subscribers_local - person.local::int + FROM + community + LEFT JOIN person ON person.id = OLD.person_id + WHERE + community.id = OLD.community_id + AND community.id = ca.community_id + AND person.local IS NOT NULL; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION delete_follow_before_person () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + DELETE FROM community_follower AS c + WHERE c.person_id = OLD.id; + RETURN OLD; +END; +$$; + +CREATE FUNCTION person_aggregates_comment_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + person_aggregates + SET + comment_count = comment_count + 1 + WHERE + person_id = NEW.creator_id; + ELSIF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + person_aggregates + SET + comment_count = comment_count - 1 + WHERE + person_id = OLD.creator_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION person_aggregates_comment_score () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + -- Need to get the post creator, not the voter + UPDATE + person_aggregates ua + SET + comment_score = comment_score + NEW.score + FROM + comment c + WHERE + ua.person_id = c.creator_id + AND c.id = NEW.comment_id; + ELSIF (TG_OP = 'DELETE') THEN + UPDATE + person_aggregates ua + SET + comment_score = comment_score - OLD.score + FROM + comment c + WHERE + ua.person_id = c.creator_id + AND c.id = OLD.comment_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION person_aggregates_person () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + INSERT INTO person_aggregates (person_id) + VALUES (NEW.id); + ELSIF (TG_OP = 'DELETE') THEN + DELETE FROM person_aggregates + WHERE person_id = OLD.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION person_aggregates_post_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + person_aggregates + SET + post_count = post_count + 1 + WHERE + person_id = NEW.creator_id; + ELSIF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + person_aggregates + SET + post_count = post_count - 1 + WHERE + person_id = OLD.creator_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION person_aggregates_post_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + person_aggregates + SET + post_count = post_count + post_group.count + FROM ( + SELECT + creator_id, + count(*) + FROM + new_post + GROUP BY + creator_id) post_group +WHERE + person_aggregates.person_id = post_group.creator_id; + RETURN NULL; +END +$$; + +CREATE FUNCTION person_aggregates_post_score () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + -- Need to get the post creator, not the voter + UPDATE + person_aggregates ua + SET + post_score = post_score + NEW.score + FROM + post p + WHERE + ua.person_id = p.creator_id + AND p.id = NEW.post_id; + ELSIF (TG_OP = 'DELETE') THEN + UPDATE + person_aggregates ua + SET + post_score = post_score - OLD.score + FROM + post p + WHERE + ua.person_id = p.creator_id + AND p.id = OLD.post_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION post_aggregates_comment_count () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + -- Check for post existence - it may not exist anymore + IF TG_OP = 'INSERT' OR EXISTS ( + SELECT + 1 + FROM + post p + WHERE + p.id = OLD.post_id) THEN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + post_aggregates pa + SET + comments = comments + 1 + WHERE + pa.post_id = NEW.post_id; + ELSIF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + post_aggregates pa + SET + comments = comments - 1 + WHERE + pa.post_id = OLD.post_id; + END IF; + END IF; + IF TG_OP = 'INSERT' THEN + UPDATE + post_aggregates pa + SET + newest_comment_time = NEW.published + WHERE + pa.post_id = NEW.post_id; + -- A 2 day necro-bump limit + UPDATE + post_aggregates pa + SET + newest_comment_time_necro = NEW.published + FROM + post p + WHERE + pa.post_id = p.id + AND pa.post_id = NEW.post_id + -- Fix issue with being able to necro-bump your own post + AND NEW.creator_id != p.creator_id + AND pa.published > ('now'::timestamp - '2 days'::interval); + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION post_aggregates_featured_community () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + post_aggregates pa + SET + featured_community = NEW.featured_community + WHERE + pa.post_id = NEW.id; + RETURN NULL; +END +$$; + +CREATE FUNCTION post_aggregates_featured_local () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + post_aggregates pa + SET + featured_local = NEW.featured_local + WHERE + pa.post_id = NEW.id; + RETURN NULL; +END +$$; + +CREATE FUNCTION post_aggregates_post () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO post_aggregates (post_id, published, newest_comment_time, newest_comment_time_necro, community_id, creator_id, instance_id) + SELECT + id, + published, + published, + published, + community_id, + creator_id, + ( + SELECT + community.instance_id + FROM + community + WHERE + community.id = community_id + LIMIT 1) +FROM + new_post; + RETURN NULL; +END +$$; + +CREATE FUNCTION post_aggregates_score () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + UPDATE + post_aggregates pa + SET + score = score + NEW.score, + upvotes = CASE WHEN NEW.score = 1 THEN + upvotes + 1 + ELSE + upvotes + END, + downvotes = CASE WHEN NEW.score = - 1 THEN + downvotes + 1 + ELSE + downvotes + END, + controversy_rank = controversy_rank (pa.upvotes + CASE WHEN NEW.score = 1 THEN + 1 + ELSE + 0 + END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN + 1 + ELSE + 0 + END::numeric) + WHERE + pa.post_id = NEW.post_id; + ELSIF (TG_OP = 'DELETE') THEN + -- Join to post because that post may not exist anymore + UPDATE + post_aggregates pa + SET + score = score - OLD.score, + upvotes = CASE WHEN OLD.score = 1 THEN + upvotes - 1 + ELSE + upvotes + END, + downvotes = CASE WHEN OLD.score = - 1 THEN + downvotes - 1 + ELSE + downvotes + END, + controversy_rank = controversy_rank (pa.upvotes + CASE WHEN NEW.score = 1 THEN + 1 + ELSE + 0 + END::numeric, pa.downvotes + CASE WHEN NEW.score = - 1 THEN + 1 + ELSE + 0 + END::numeric) + FROM + post p + WHERE + pa.post_id = p.id + AND pa.post_id = OLD.post_id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_comment_delete () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + comments = comments - 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_comment_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + comments = comments + 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_community_delete () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + communities = communities - 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_community_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + communities = communities + 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_person_delete () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + -- Join to site since the creator might not be there anymore + UPDATE + site_aggregates sa + SET + users = users - 1 + FROM + site s + WHERE + sa.site_id = s.id; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_person_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + site_aggregates + SET + users = users + 1; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_post_delete () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_removed_or_deleted (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + posts = posts - 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_post_insert () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + site_aggregates sa + SET + posts = posts + ( + SELECT + count(*) + FROM + new_post) + FROM + site s + WHERE + sa.site_id = s.id; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_post_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + IF (was_restored_or_created (TG_OP, OLD, NEW)) THEN + UPDATE + site_aggregates sa + SET + posts = posts + 1 + FROM + site s + WHERE + sa.site_id = s.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION site_aggregates_site () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + -- we only ever want to have a single value in site_aggregate because the site_aggregate triggers update all rows in that table. + -- a cleaner check would be to insert it for the local_site but that would break assumptions at least in the tests + IF (TG_OP = 'INSERT') AND NOT EXISTS ( + SELECT + * + FROM + site_aggregates + LIMIT 1) THEN + INSERT INTO site_aggregates (site_id) + VALUES (NEW.id); + ELSIF (TG_OP = 'DELETE') THEN + DELETE FROM site_aggregates + WHERE site_id = OLD.id; + END IF; + RETURN NULL; +END +$$; + +CREATE FUNCTION was_removed_or_deleted (tg_op text, old record, new record) + RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'INSERT') THEN + RETURN FALSE; + END IF; + IF (TG_OP = 'DELETE' AND OLD.deleted = 'f' AND OLD.removed = 'f') THEN + RETURN TRUE; + END IF; + RETURN TG_OP = 'UPDATE' + AND OLD.deleted = 'f' + AND OLD.removed = 'f' + AND (NEW.deleted = 't' + OR NEW.removed = 't'); +END +$$; + +CREATE FUNCTION was_restored_or_created (tg_op text, old record, new record) + RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + IF (TG_OP = 'DELETE') THEN + RETURN FALSE; + END IF; + IF (TG_OP = 'INSERT') THEN + RETURN TRUE; + END IF; + RETURN TG_OP = 'UPDATE' + AND NEW.deleted = 'f' + AND NEW.removed = 'f' + AND (OLD.deleted = 't' + OR OLD.removed = 't'); +END +$$; + +CREATE TRIGGER comment_aggregates_comment + AFTER INSERT OR DELETE ON comment + FOR EACH ROW + EXECUTE FUNCTION comment_aggregates_comment (); + +CREATE TRIGGER comment_aggregates_score + AFTER INSERT OR DELETE ON comment_like + FOR EACH ROW + EXECUTE FUNCTION comment_aggregates_score (); + +CREATE TRIGGER community_aggregates_comment_count + AFTER INSERT OR DELETE OR UPDATE OF removed, + deleted ON comment + FOR EACH ROW + EXECUTE FUNCTION community_aggregates_comment_count (); + +CREATE TRIGGER community_aggregates_community + AFTER INSERT OR DELETE ON community + FOR EACH ROW + EXECUTE FUNCTION community_aggregates_community (); + +CREATE TRIGGER community_aggregates_post_count + AFTER DELETE OR UPDATE OF removed, + deleted ON post + FOR EACH ROW + EXECUTE FUNCTION community_aggregates_post_count (); + +CREATE TRIGGER community_aggregates_post_count_insert + AFTER INSERT ON post REFERENCING NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION community_aggregates_post_count_insert (); + +CREATE TRIGGER community_aggregates_subscriber_count + AFTER INSERT OR DELETE ON community_follower + FOR EACH ROW + EXECUTE FUNCTION community_aggregates_subscriber_count (); + +CREATE TRIGGER delete_follow_before_person + BEFORE DELETE ON person + FOR EACH ROW + EXECUTE FUNCTION delete_follow_before_person (); + +CREATE TRIGGER person_aggregates_comment_count + AFTER INSERT OR DELETE OR UPDATE OF removed, + deleted ON comment + FOR EACH ROW + EXECUTE FUNCTION person_aggregates_comment_count (); + +CREATE TRIGGER person_aggregates_comment_score + AFTER INSERT OR DELETE ON comment_like + FOR EACH ROW + EXECUTE FUNCTION person_aggregates_comment_score (); + +CREATE TRIGGER person_aggregates_person + AFTER INSERT OR DELETE ON person + FOR EACH ROW + EXECUTE FUNCTION person_aggregates_person (); + +CREATE TRIGGER person_aggregates_post_count + AFTER DELETE OR UPDATE OF removed, + deleted ON post + FOR EACH ROW + EXECUTE FUNCTION person_aggregates_post_count (); + +CREATE TRIGGER person_aggregates_post_insert + AFTER INSERT ON post REFERENCING NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION person_aggregates_post_insert (); + +CREATE TRIGGER person_aggregates_post_score + AFTER INSERT OR DELETE ON post_like + FOR EACH ROW + EXECUTE FUNCTION person_aggregates_post_score (); + +CREATE TRIGGER post_aggregates_comment_count + AFTER INSERT OR DELETE OR UPDATE OF removed, + deleted ON comment + FOR EACH ROW + EXECUTE FUNCTION post_aggregates_comment_count (); + +CREATE TRIGGER post_aggregates_featured_community + AFTER UPDATE ON post + FOR EACH ROW + WHEN ((old.featured_community IS DISTINCT FROM new.featured_community)) + EXECUTE FUNCTION post_aggregates_featured_community (); + +CREATE TRIGGER post_aggregates_featured_local + AFTER UPDATE ON post + FOR EACH ROW + WHEN ((old.featured_local IS DISTINCT FROM new.featured_local)) + EXECUTE FUNCTION post_aggregates_featured_local (); + +CREATE TRIGGER post_aggregates_post + AFTER INSERT ON post REFERENCING NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION post_aggregates_post (); + +CREATE TRIGGER post_aggregates_score + AFTER INSERT OR DELETE ON post_like + FOR EACH ROW + EXECUTE FUNCTION post_aggregates_score (); + +CREATE TRIGGER site_aggregates_comment_delete + AFTER DELETE OR UPDATE OF removed, + deleted ON comment + FOR EACH ROW + WHEN ((old.local = TRUE)) + EXECUTE FUNCTION site_aggregates_comment_delete (); + +CREATE TRIGGER site_aggregates_comment_insert + AFTER INSERT OR UPDATE OF removed, + deleted ON comment + FOR EACH ROW + WHEN ((new.local = TRUE)) + EXECUTE FUNCTION site_aggregates_comment_insert (); + +CREATE TRIGGER site_aggregates_community_insert + AFTER INSERT OR UPDATE OF removed, + deleted ON community + FOR EACH ROW + WHEN ((new.local = TRUE)) + EXECUTE FUNCTION site_aggregates_community_insert (); + +CREATE TRIGGER site_aggregates_person_delete + AFTER DELETE ON person + FOR EACH ROW + WHEN ((old.local = TRUE)) + EXECUTE FUNCTION site_aggregates_person_delete (); + +CREATE TRIGGER site_aggregates_person_insert + AFTER INSERT ON person + FOR EACH ROW + WHEN ((new.local = TRUE)) + EXECUTE FUNCTION site_aggregates_person_insert (); + +CREATE TRIGGER site_aggregates_post_delete + AFTER DELETE OR UPDATE OF removed, + deleted ON post + FOR EACH ROW + WHEN ((old.local = TRUE)) + EXECUTE FUNCTION site_aggregates_post_delete (); + +CREATE TRIGGER site_aggregates_post_insert + AFTER INSERT ON post REFERENCING NEW TABLE AS new_post + FOR EACH STATEMENT + EXECUTE FUNCTION site_aggregates_post_insert (); + +CREATE TRIGGER site_aggregates_post_update + AFTER UPDATE OF removed, + deleted ON post + FOR EACH ROW + WHEN ((new.local = TRUE)) + EXECUTE FUNCTION site_aggregates_post_update (); + +CREATE TRIGGER site_aggregates_site + AFTER INSERT OR DELETE ON site + FOR EACH ROW + EXECUTE FUNCTION site_aggregates_site (); + +-- Rank functions +CREATE FUNCTION controversy_rank (upvotes numeric, downvotes numeric) + RETURNS double precision + LANGUAGE plpgsql + IMMUTABLE + AS $$ +BEGIN + IF downvotes <= 0 OR upvotes <= 0 THEN + RETURN 0; + ELSE + RETURN (upvotes + downvotes) * CASE WHEN upvotes > downvotes THEN + downvotes::float / upvotes::float + ELSE + upvotes::float / downvotes::float + END; + END IF; +END; +$$; + +CREATE FUNCTION hot_rank (score numeric, published timestamp with time zone) + RETURNS double precision + LANGUAGE plpgsql + IMMUTABLE PARALLEL SAFE + AS $$ +DECLARE + hours_diff numeric := EXTRACT(EPOCH FROM (now() - published)) / 3600; +BEGIN + -- 24 * 7 = 168, so after a week, it will default to 0. + IF (hours_diff > 0 AND hours_diff < 168) THEN + -- Use greatest(2,score), so that the hot_rank will be positive and not ignored. + RETURN log(greatest (2, score + 2)) / power((hours_diff + 2), 1.8); + ELSE + -- if the post is from the future, set hot score to 0. otherwise you can game the post to + -- always be on top even with only 1 vote by setting it to the future + RETURN 0.0; + END IF; +END; +$$; + +CREATE FUNCTION scaled_rank (score numeric, published timestamp with time zone, users_active_month numeric) + RETURNS double precision + LANGUAGE plpgsql + IMMUTABLE PARALLEL SAFE + AS $$ +BEGIN + -- Add 2 to avoid divide by zero errors + -- Default for score = 1, active users = 1, and now, is (0.1728 / log(2 + 1)) = 0.3621 + -- There may need to be a scale factor multiplied to users_active_month, to make + -- the log curve less pronounced. This can be tuned in the future. + RETURN (hot_rank (score, published) / log(2 + users_active_month)); +END; +$$; + +-- Don't defer constraints +ALTER TABLE comment_aggregates + ALTER CONSTRAINT comment_aggregates_comment_id_fkey NOT DEFERRABLE; + +ALTER TABLE community_aggregates + ALTER CONSTRAINT community_aggregates_community_id_fkey NOT DEFERRABLE; + +ALTER TABLE person_aggregates + ALTER CONSTRAINT person_aggregates_person_id_fkey NOT DEFERRABLE; + +ALTER TABLE post_aggregates + ALTER CONSTRAINT post_aggregates_community_id_fkey NOT DEFERRABLE, + ALTER CONSTRAINT post_aggregates_creator_id_fkey NOT DEFERRABLE, + ALTER CONSTRAINT post_aggregates_instance_id_fkey NOT DEFERRABLE, + ALTER CONSTRAINT post_aggregates_post_id_fkey NOT DEFERRABLE; + +ALTER TABLE site_aggregates + ALTER CONSTRAINT site_aggregates_site_id_fkey NOT DEFERRABLE; + diff --git a/migrations/2024-02-24-034523_replaceable-schema/up.sql b/migrations/2024-02-24-034523_replaceable-schema/up.sql new file mode 100644 index 000000000..05e57afc0 --- /dev/null +++ b/migrations/2024-02-24-034523_replaceable-schema/up.sql @@ -0,0 +1,81 @@ +CREATE UNIQUE INDEX idx_site_aggregates_1_row_only ON site_aggregates ((TRUE)); + +-- Drop functions and use `CASCADE` to drop the triggers that use them +DROP FUNCTION comment_aggregates_comment, comment_aggregates_score, community_aggregates_comment_count, community_aggregates_community, community_aggregates_post_count, community_aggregates_post_count_insert, community_aggregates_subscriber_count, delete_follow_before_person, person_aggregates_comment_count, person_aggregates_comment_score, person_aggregates_person, person_aggregates_post_count, person_aggregates_post_insert, person_aggregates_post_score, post_aggregates_comment_count, post_aggregates_featured_community, post_aggregates_featured_local, post_aggregates_post, post_aggregates_score, site_aggregates_comment_delete, site_aggregates_comment_insert, site_aggregates_community_delete, site_aggregates_community_insert, site_aggregates_person_delete, site_aggregates_person_insert, site_aggregates_post_delete, site_aggregates_post_insert, site_aggregates_post_update, site_aggregates_site, was_removed_or_deleted, was_restored_or_created CASCADE; + +-- Drop rank functions +DROP FUNCTION controversy_rank, scaled_rank, hot_rank; + +-- Defer constraints +ALTER TABLE comment_aggregates + ALTER CONSTRAINT comment_aggregates_comment_id_fkey INITIALLY DEFERRED; + +ALTER TABLE community_aggregates + ALTER CONSTRAINT community_aggregates_community_id_fkey INITIALLY DEFERRED; + +ALTER TABLE person_aggregates + ALTER CONSTRAINT person_aggregates_person_id_fkey INITIALLY DEFERRED; + +ALTER TABLE post_aggregates + ALTER CONSTRAINT post_aggregates_community_id_fkey INITIALLY DEFERRED, + ALTER CONSTRAINT post_aggregates_creator_id_fkey INITIALLY DEFERRED, + ALTER CONSTRAINT post_aggregates_instance_id_fkey INITIALLY DEFERRED, + ALTER CONSTRAINT post_aggregates_post_id_fkey INITIALLY DEFERRED; + +ALTER TABLE site_aggregates + ALTER CONSTRAINT site_aggregates_site_id_fkey INITIALLY DEFERRED; + +-- Fix values that might be incorrect because of the old triggers +UPDATE + post_aggregates +SET + featured_local = post.featured_local, + featured_community = post.featured_community +FROM + post +WHERE + post_aggregates.post_id = post.id + AND (post_aggregates.featured_local, + post_aggregates.featured_community) != (post.featured_local, + post.featured_community); + +UPDATE + community_aggregates +SET + comments = counted.comments +FROM ( + SELECT + community_id, + count(*) AS comments + FROM + comment, + LATERAL ( + SELECT + * + FROM + post + WHERE + post.id = comment.post_id + LIMIT 1) AS post + WHERE + NOT (comment.deleted + OR comment.removed + OR post.deleted + OR post.removed) + GROUP BY + community_id) AS counted +WHERE + community_aggregates.community_id = counted.community_id + AND community_aggregates.comments != counted.comments; + +UPDATE + site_aggregates +SET + communities = ( + SELECT + count(*) + FROM + community + WHERE + local); + diff --git a/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/down.sql b/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/down.sql new file mode 100644 index 000000000..484cbef5b --- /dev/null +++ b/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/down.sql @@ -0,0 +1,10 @@ +ALTER TABLE local_user_vote_display_mode + DROP COLUMN score, + ADD COLUMN score boolean DEFAULT TRUE NOT NULL, + DROP COLUMN upvotes, + ADD COLUMN upvotes boolean DEFAULT FALSE NOT NULL, + DROP COLUMN downvotes, + ADD COLUMN downvotes boolean DEFAULT FALSE NOT NULL, + DROP COLUMN upvote_percentage, + ADD COLUMN upvote_percentage boolean DEFAULT TRUE NOT NULL; + diff --git a/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/up.sql b/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/up.sql new file mode 100644 index 000000000..39a01eb07 --- /dev/null +++ b/migrations/2024-04-05-153647_alter_vote_display_mode_defaults/up.sql @@ -0,0 +1,14 @@ +-- Based on a poll, update the local_user_vote_display_mode defaults to: +-- Upvotes + Downvotes +-- Rather than +-- Score + upvote_percentage +ALTER TABLE local_user_vote_display_mode + DROP COLUMN score, + ADD COLUMN score boolean DEFAULT FALSE NOT NULL, + DROP COLUMN upvotes, + ADD COLUMN upvotes boolean DEFAULT TRUE NOT NULL, + DROP COLUMN downvotes, + ADD COLUMN downvotes boolean DEFAULT TRUE NOT NULL, + DROP COLUMN upvote_percentage, + ADD COLUMN upvote_percentage boolean DEFAULT FALSE NOT NULL; + diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..1911d651b --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "schedule": ["before 4am on the first day of the month"] +} diff --git a/scripts/dump_schema.sh b/scripts/dump_schema.sh new file mode 100755 index 000000000..f783be26b --- /dev/null +++ b/scripts/dump_schema.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e + +# Dumps database schema, not including things that are added outside of migrations + +CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" + +cd $CWD/../ + +source scripts/start_dev_db.sh + +diesel migration run +pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f schema.sqldump + +pg_ctl stop +rm -rf $PGDATA diff --git a/scripts/sql_format_check.sh b/scripts/sql_format_check.sh index a75425da8..fabc3b3ed 100755 --- a/scripts/sql_format_check.sh +++ b/scripts/sql_format_check.sh @@ -9,10 +9,12 @@ cd $CWD/../ # Copy the files to a temp dir TMP_DIR=$(mktemp -d) -cp -a migrations/. $TMP_DIR +cp -a migrations/. $TMP_DIR/migrations +cp -a crates/db_schema/replaceable_schema/. $TMP_DIR/replaceable_schema # Format the new files find $TMP_DIR -type f -name '*.sql' -exec pg_format -i {} + # Diff the directories -diff -r migrations $TMP_DIR +diff -r migrations $TMP_DIR/migrations +diff -r crates/db_schema/replaceable_schema $TMP_DIR/replaceable_schema diff --git a/scripts/start_dev_db.sh b/scripts/start_dev_db.sh index 8ea4a294e..5965316ba 100644 --- a/scripts/start_dev_db.sh +++ b/scripts/start_dev_db.sh @@ -2,8 +2,10 @@ export PGDATA="$PWD/dev_pgdata" export PGHOST=$PWD +export PGUSER=postgres export DATABASE_URL="postgresql://lemmy:password@/lemmy?host=$PWD" export LEMMY_DATABASE_URL=$DATABASE_URL +export PGDATABASE=lemmy # If cluster exists, stop the server and delete the cluster if [[ -d $PGDATA ]] @@ -44,5 +46,5 @@ pg_ctl init --silent --options="--username=postgres --auth=trust --no-instructio pg_ctl start --silent --options="${config_args[*]}" # Setup database -psql --quiet -c "CREATE USER lemmy WITH PASSWORD 'password' SUPERUSER;" -U postgres -psql --quiet -c "CREATE DATABASE lemmy WITH OWNER lemmy;" -U postgres +PGDATABASE=postgres psql --quiet -c "CREATE USER lemmy WITH PASSWORD 'password' SUPERUSER;" +PGDATABASE=postgres psql --quiet -c "CREATE DATABASE lemmy WITH OWNER lemmy;" diff --git a/scripts/test-with-coverage.sh b/scripts/test-with-coverage.sh new file mode 100755 index 000000000..e4dfcddf8 --- /dev/null +++ b/scripts/test-with-coverage.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e + +CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" + +cd $CWD/../ + +PACKAGE="$1" +echo "$PACKAGE" + +source scripts/start_dev_db.sh + +# tests are executed in working directory crates/api (or similar), +# so to load the config we need to traverse to the repo root +export LEMMY_CONFIG_LOCATION=../../config/config.hjson +export RUST_BACKTRACE=1 + +cargo install cargo-llvm-cov + +# Create lcov.info file, which is used by things like the Coverage Gutters extension for VS Code +cargo llvm-cov --workspace --all-features --no-fail-fast --lcov --output-path target/lcov.info + +# Add this to do printlns: -- --nocapture + +pg_ctl stop --silent +rm -rf $PGDATA diff --git a/scripts/test.sh b/scripts/test.sh index efe9b1513..3e0581fc7 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -6,7 +6,7 @@ CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" cd $CWD/../ PACKAGE="$1" -echo "$PACKAGE" +TEST="$2" source scripts/start_dev_db.sh @@ -17,7 +17,7 @@ export RUST_BACKTRACE=1 if [ -n "$PACKAGE" ]; then - cargo test -p $PACKAGE --all-features --no-fail-fast + cargo test -p $PACKAGE --all-features --no-fail-fast $TEST else cargo test --workspace --no-fail-fast fi diff --git a/scripts/upgrade_deps.sh b/scripts/upgrade_deps.sh index 0c4ae6f0f..b20e9f35c 100755 --- a/scripts/upgrade_deps.sh +++ b/scripts/upgrade_deps.sh @@ -6,9 +6,9 @@ pushd ../ cargo udeps --all-targets # Upgrade deps -cargo upgrade --workspace +cargo upgrade -# Run check -cargo check +# Run clippy +cargo clippy popd diff --git a/src/code_migrations.rs b/src/code_migrations.rs index cee02075c..05b564f47 100644 --- a/src/code_migrations.rs +++ b/src/code_migrations.rs @@ -34,14 +34,14 @@ use lemmy_db_schema::{ traits::Crud, utils::{get_conn, naive_now, DbPool}, }; -use lemmy_utils::{error::LemmyError, settings::structs::Settings}; +use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use tracing::info; use url::Url; pub async fn run_advanced_migrations( pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { let protocol_and_hostname = &settings.get_protocol_and_hostname(); user_updates_2020_04_02(pool, protocol_and_hostname).await?; community_updates_2020_04_02(pool, protocol_and_hostname).await?; @@ -60,7 +60,7 @@ pub async fn run_advanced_migrations( async fn user_updates_2020_04_02( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::person::dsl::{actor_id, local, person}; let conn = &mut get_conn(pool).await?; @@ -99,7 +99,7 @@ async fn user_updates_2020_04_02( async fn community_updates_2020_04_02( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::community::dsl::{actor_id, community, local}; let conn = &mut get_conn(pool).await?; @@ -139,7 +139,7 @@ async fn community_updates_2020_04_02( async fn post_updates_2020_04_03( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::post::dsl::{ap_id, local, post}; let conn = &mut get_conn(pool).await?; @@ -177,7 +177,7 @@ async fn post_updates_2020_04_03( async fn comment_updates_2020_04_03( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::comment::dsl::{ap_id, comment, local}; let conn = &mut get_conn(pool).await?; @@ -215,7 +215,7 @@ async fn comment_updates_2020_04_03( async fn private_message_updates_2020_05_05( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::private_message::dsl::{ap_id, local, private_message}; let conn = &mut get_conn(pool).await?; @@ -253,7 +253,7 @@ async fn private_message_updates_2020_05_05( async fn post_thumbnail_url_updates_2020_07_27( pool: &mut DbPool<'_>, protocol_and_hostname: &str, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { use lemmy_db_schema::schema::post::dsl::{post, thumbnail_url}; let conn = &mut get_conn(pool).await?; @@ -282,10 +282,7 @@ async fn post_thumbnail_url_updates_2020_07_27( /// We are setting inbox and follower URLs for local and remote actors alike, because for now /// all federated instances are also Lemmy and use the same URL scheme. -async fn apub_columns_2021_02_02( - pool: &mut DbPool<'_>, - settings: &Settings, -) -> Result<(), LemmyError> { +async fn apub_columns_2021_02_02(pool: &mut DbPool<'_>, settings: &Settings) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; info!("Running apub_columns_2021_02_02"); { @@ -346,9 +343,9 @@ async fn instance_actor_2022_01_28( pool: &mut DbPool<'_>, protocol_and_hostname: &str, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { info!("Running instance_actor_2021_09_29"); - if let Ok(site_view) = SiteView::read_local(pool).await { + if let Ok(Some(site_view)) = SiteView::read_local(pool).await { let site = site_view.site; // if site already has public key, we dont need to do anything here if !site.public_key.is_empty() { @@ -374,7 +371,7 @@ async fn instance_actor_2022_01_28( /// key field is empty, generate a new keypair. It would be possible to regenerate only the pubkey, /// but thats more complicated and has no benefit, as federation is already broken for these actors. /// https://github.com/LemmyNet/lemmy/issues/2347 -async fn regenerate_public_keys_2022_07_05(pool: &mut DbPool<'_>) -> Result<(), LemmyError> { +async fn regenerate_public_keys_2022_07_05(pool: &mut DbPool<'_>) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; info!("Running regenerate_public_keys_2022_07_05"); @@ -433,7 +430,7 @@ async fn regenerate_public_keys_2022_07_05(pool: &mut DbPool<'_>) -> Result<(), async fn initialize_local_site_2022_10_10( pool: &mut DbPool<'_>, settings: &Settings, -) -> Result<(), LemmyError> { +) -> LemmyResult<()> { info!("Running initialize_local_site_2022_10_10"); // Check to see if local_site exists diff --git a/src/lib.rs b/src/lib.rs index 777c5d9ce..633fd5313 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ use actix_web::{ App, HttpResponse, HttpServer, - Result, }; use actix_web_prom::PrometheusMetricsBuilder; use clap::Parser; @@ -45,7 +44,7 @@ use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool}; use lemmy_federate::{start_stop_federation_workers_cancellable, Opts}; use lemmy_routes::{feeds, images, nodeinfo, webfinger}; use lemmy_utils::{ - error::LemmyError, + error::LemmyResult, rate_limit::RateLimitCell, response::jsonify_plain_text_errors, settings::{structs::Settings, SETTINGS}, @@ -107,7 +106,7 @@ pub struct CmdArgs { } /// Placing the main function in lib.rs allows other crates to import it and embed Lemmy -pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { +pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> { // Print version number to log println!("Lemmy v{VERSION}"); @@ -125,12 +124,12 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { // Initialize the secrets let secret = Secret::init(&mut (&pool).into()) - .await + .await? .expect("Couldn't initialize secrets."); // Make sure the local site is set up. let site_view = SiteView::read_local(&mut (&pool).into()) - .await + .await? .expect("local site not set up"); let local_site = site_view.local_site; let federation_enabled = local_site.federation_enabled; @@ -219,15 +218,17 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { let mut interrupt = tokio::signal::unix::signal(SignalKind::interrupt())?; let mut terminate = tokio::signal::unix::signal(SignalKind::terminate())?; - tokio::select! { - _ = tokio::signal::ctrl_c() => { - tracing::warn!("Received ctrl-c, shutting down gracefully..."); - } - _ = interrupt.recv() => { - tracing::warn!("Received interrupt, shutting down gracefully..."); - } - _ = terminate.recv() => { - tracing::warn!("Received terminate, shutting down gracefully..."); + if server.is_some() || federate.is_some() { + tokio::select! { + _ = tokio::signal::ctrl_c() => { + tracing::warn!("Received ctrl-c, shutting down gracefully..."); + } + _ = interrupt.recv() => { + tracing::warn!("Received interrupt, shutting down gracefully..."); + } + _ = terminate.recv() => { + tracing::warn!("Received terminate, shutting down gracefully..."); + } } } if let Some(server) = server { @@ -244,7 +245,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> { } /// Creates temporary HTTP server which returns status 503 for all requests. -fn create_startup_server() -> Result { +fn create_startup_server() -> LemmyResult { let startup_server = HttpServer::new(move || { App::new().wrap(ErrorHandlers::new().default_handler(move |req| { let (req, _) = req.into_parts(); @@ -267,7 +268,7 @@ fn create_http_server( federation_config: FederationConfig, settings: Settings, federation_enabled: bool, -) -> Result { +) -> LemmyResult { // this must come before the HttpServer creation // creates a middleware that populates http metrics for each path, method, and status code let prom_api_metrics = PrometheusMetricsBuilder::new("lemmy_api") @@ -349,7 +350,7 @@ fn cors_config(settings: &Settings) -> Cors { } } -pub fn init_logging(opentelemetry_url: &Option) -> Result<(), LemmyError> { +pub fn init_logging(opentelemetry_url: &Option) -> LemmyResult<()> { LogTracer::init()?; let log_description = env::var("RUST_LOG").unwrap_or_else(|_| "info".into()); diff --git a/src/main.rs b/src/main.rs index 1544bcecf..dd17d6eb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ use clap::Parser; use lemmy_server::{init_logging, start_lemmy_server, CmdArgs}; -use lemmy_utils::{error::LemmyError, settings::SETTINGS}; +use lemmy_utils::{error::LemmyResult, settings::SETTINGS}; #[tokio::main] -pub async fn main() -> Result<(), LemmyError> { +pub async fn main() -> LemmyResult<()> { init_logging(&SETTINGS.opentelemetry_url)?; let args = CmdArgs::parse(); diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index e225b96ff..f7904104f 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -29,13 +29,13 @@ use lemmy_db_schema::{ utils::{get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, }; use lemmy_routes::nodeinfo::NodeInfo; -use lemmy_utils::error::{LemmyError, LemmyResult}; +use lemmy_utils::error::LemmyResult; use reqwest_middleware::ClientWithMiddleware; use std::time::Duration; use tracing::{error, info, warn}; /// Schedules various cleanup tasks for lemmy in a background thread -pub async fn setup(context: LemmyContext) -> Result<(), LemmyError> { +pub async fn setup(context: LemmyContext) -> LemmyResult<()> { // Setup the connections let mut scheduler = AsyncScheduler::new(); startup_jobs(&mut context.pool()).await; @@ -131,7 +131,7 @@ async fn update_hot_ranks(pool: &mut DbPool<'_>) { &mut conn, "comment", "a.hot_rank != 0", - "SET hot_rank = hot_rank(a.score, a.published)", + "SET hot_rank = r.hot_rank(a.score, a.published)", ) .await; @@ -139,7 +139,7 @@ async fn update_hot_ranks(pool: &mut DbPool<'_>) { &mut conn, "community", "a.hot_rank != 0", - "SET hot_rank = hot_rank(a.subscribers, a.published)", + "SET hot_rank = r.hot_rank(a.subscribers, a.published)", ) .await; @@ -236,9 +236,9 @@ async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection) LIMIT $2 FOR UPDATE SKIP LOCKED) UPDATE post_aggregates pa - SET hot_rank = hot_rank(pa.score, pa.published), - hot_rank_active = hot_rank(pa.score, pa.newest_comment_time_necro), - scaled_rank = scaled_rank(pa.score, pa.published, ca.users_active_month) + SET hot_rank = r.hot_rank(pa.score, pa.published), + hot_rank_active = r.hot_rank(pa.score, pa.newest_comment_time_necro), + scaled_rank = r.scaled_rank(pa.score, pa.published, ca.users_active_month) FROM batch, community_aggregates ca WHERE pa.post_id = batch.post_id and pa.community_id = ca.community_id RETURNING pa.published; "#, @@ -477,7 +477,7 @@ async fn update_instance_software( .build(); let form = match client.get(&node_info_url).send().await { Ok(res) if res.status().is_client_error() => { - // Instance doesnt have nodeinfo but sent a response, consider it alive + // Instance doesn't have nodeinfo but sent a response, consider it alive Some(default_form) } Ok(res) => match res.json::().await { diff --git a/src/session_middleware.rs b/src/session_middleware.rs index 2bee64ca0..f4535249c 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -130,7 +130,7 @@ mod tests { let pool_ = build_db_pool_for_tests().await; let pool = &mut (&pool_).into(); - let secret = Secret::init(pool).await.unwrap(); + let secret = Secret::init(pool).await.unwrap().unwrap(); let context = LemmyContext::create( pool_.clone(), ClientBuilder::new(Client::default()).build(), diff --git a/src/telemetry.rs b/src/telemetry.rs index 2f758e062..a7a1ac809 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,5 +1,5 @@ use console_subscriber::ConsoleLayer; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::LemmyResult; use opentelemetry::{ sdk::{propagation::TraceContextPropagator, Resource}, KeyValue, @@ -8,11 +8,7 @@ use opentelemetry_otlp::WithExportConfig; use tracing::{subscriber::set_global_default, Subscriber}; use tracing_subscriber::{filter::Targets, layer::SubscriberExt, registry::LookupSpan, Layer}; -pub fn init_tracing( - opentelemetry_url: &str, - subscriber: S, - targets: Targets, -) -> Result<(), LemmyError> +pub fn init_tracing(opentelemetry_url: &str, subscriber: S, targets: Targets) -> LemmyResult<()> where S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync + 'static, {