From 6015ef045d429a48171c564f19d94d1ae13d20d7 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Fri, 20 Dec 2024 17:20:16 -0500 Subject: [PATCH 1/2] Running cargo-features-manager prune. (#5274) - Context #5273 --- Cargo.lock | 73 +++++++++++----------------- Cargo.toml | 13 ++--- crates/api_common/Cargo.toml | 4 +- crates/db_schema/Cargo.toml | 6 +-- crates/db_views_actor/Cargo.toml | 4 +- crates/db_views_moderator/Cargo.toml | 4 +- crates/federate/Cargo.toml | 2 +- crates/utils/Cargo.toml | 7 ++- 8 files changed, 46 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7215d79f..10f629cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,9 +460,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "flate2", "futures-core", @@ -495,15 +495,15 @@ dependencies = [ [[package]] name = "atom_syndication" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3a5ed3201df5658d1aa45060c5a57dc9dba8a8ada20d696d67cb0c479ee043" +checksum = "3ee79fb83c725eae67b55218870813d2fc39fd85e4f1583848ef9f4f823cfe7c" dependencies = [ "chrono", "derive_builder", "diligent-date-parser", "never", - "quick-xml 0.36.1", + "quick-xml 0.37.1", ] [[package]] @@ -514,21 +514,20 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +checksum = "f47bb8cc16b669d267eeccf585aea077d0882f4777b1c1f740217885d6e6e5a3" dependencies = [ "aws-lc-sys", - "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.21.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234314bd569802ec87011d653d6815c6d7b9ffb969e9fee5b8b20ef860e8dce9" +checksum = "a2101df3813227bbaaaa0b04cd61c534c7954b22bd68d399b440be937dc63ff7" dependencies = [ "bindgen", "cc", @@ -618,9 +617,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.6.0", "cexpr", @@ -907,9 +906,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" dependencies = [ "cc", ] @@ -1374,9 +1373,9 @@ dependencies = [ [[package]] name = "diligent-date-parser" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182" +checksum = "c8ede7d79366f419921e2e2f67889c12125726692a313bffb474bd5f37a581e9" dependencies = [ "chrono", ] @@ -1471,9 +1470,9 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" +checksum = "ea3d894bbbab314476b265f9b2d46bf24b123a36dd0e96b06a1b49545b9d9dcc" dependencies = [ "base64 0.22.1", "memchr", @@ -1599,9 +1598,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fdeflate" @@ -2897,12 +2896,12 @@ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -3219,12 +3218,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - [[package]] name = "mockall" version = "0.13.0" @@ -3598,9 +3591,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plist" @@ -3725,9 +3718,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn 2.0.87", @@ -3821,16 +3814,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc" -dependencies = [ - "encoding_rs", - "memchr", -] - [[package]] name = "quick-xml" version = "0.37.1" @@ -4324,9 +4307,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" diff --git a/Cargo.toml b/Cargo.toml index b1553be5c..df10a78b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,21 +103,21 @@ diesel-async = "0.5.1" serde = { version = "1.0.215", features = ["derive"] } serde_with = "3.9.0" actix-web = { version = "4.9.0", default-features = false, features = [ - "macros", - "rustls-0_23", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", + "macros", + "rustls-0_23", ] } tracing = "0.1.40" tracing-actix-web = { version = "0.7.10", default-features = false } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } url = { version = "2.5.3", features = ["serde"] } reqwest = { version = "0.12.7", default-features = false, features = [ - "json", "blocking", "gzip", + "json", "rustls-tls", ] } reqwest-middleware = "0.3.3" @@ -126,17 +126,15 @@ clokwerk = "0.4.0" doku = { version = "0.21.1", features = ["url-2"] } bcrypt = "0.15.1" chrono = { version = "0.4.38", features = [ - "serde", "now", + "serde", ], default-features = false } serde_json = { version = "1.0.132", features = ["preserve_order"] } base64 = "0.22.1" uuid = { version = "1.11.0", features = ["serde"] } async-trait = "0.1.83" captcha = "0.0.9" -anyhow = { version = "1.0.93", features = [ - "backtrace", -] } # backtrace is on by default on nightly, but not stable rust +anyhow = { version = "1.0.93", features = ["backtrace"] } diesel_ltree = "0.3.1" serial_test = "3.2.0" tokio = { version = "1.41.1", features = ["full"] } @@ -149,7 +147,6 @@ futures = "0.3.31" http = "1.1" rosetta-i18n = "0.1.3" ts-rs = { version = "10.0.0", features = [ - "serde-compat", "chrono-impl", "no-serde-warnings", "url-impl", diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 74a0390ca..b9e8a5a76 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -67,9 +67,9 @@ urlencoding = { workspace = true } mime = { version = "0.3.17", optional = true } mime_guess = "2.0.5" infer = "0.16.0" -webpage = { version = "2.0", default-features = false, features = [ +webpage = { version = "2.0", default-features = false, optional = true, features = [ "serde", -], optional = true } +] } encoding_rs = { version = "0.8.35", optional = true } jsonwebtoken = { version = "9.3.0", optional = true } diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index eac9d6ddd..a511508f8 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -52,8 +52,8 @@ activitypub_federation = { workspace = true, optional = true } lemmy_utils = { workspace = true, optional = true } bcrypt = { workspace = true, optional = true } diesel = { workspace = true, features = [ - "postgres", "chrono", + "postgres", "serde_json", "uuid", ], optional = true } @@ -61,14 +61,14 @@ diesel-derive-newtype = { workspace = true, optional = true } diesel-derive-enum = { workspace = true, optional = true } diesel_migrations = { workspace = true, optional = true } diesel-async = { workspace = true, features = [ - "postgres", "deadpool", + "postgres", ], optional = true } regex = { workspace = true, optional = true } diesel_ltree = { workspace = true, optional = true } async-trait = { workspace = true } tracing = { workspace = true } -deadpool = { version = "0.12.1", features = ["rt_tokio_1"], optional = true } +deadpool = { version = "0.12.1", optional = true, features = ["rt_tokio_1"] } ts-rs = { workspace = true, optional = true } futures-util = { workspace = true } tokio = { workspace = true, optional = true } diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index 18a79826b..00f8bdcaf 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -26,13 +26,13 @@ full = [ [dependencies] lemmy_db_schema = { workspace = true } diesel = { workspace = true, features = [ - "postgres", "chrono", + "postgres", "serde_json", ], optional = true } diesel-async = { workspace = true, features = [ - "postgres", "deadpool", + "postgres", ], optional = true } serde = { workspace = true } serde_with = { workspace = true } diff --git a/crates/db_views_moderator/Cargo.toml b/crates/db_views_moderator/Cargo.toml index df8ec1ea2..ae598fa32 100644 --- a/crates/db_views_moderator/Cargo.toml +++ b/crates/db_views_moderator/Cargo.toml @@ -20,13 +20,13 @@ full = ["lemmy_db_schema/full", "diesel", "diesel-async", "ts-rs"] [dependencies] lemmy_db_schema = { workspace = true } diesel = { workspace = true, features = [ - "postgres", "chrono", + "postgres", "serde_json", ], optional = true } diesel-async = { workspace = true, features = [ - "postgres", "deadpool", + "postgres", ], optional = true } serde = { workspace = true } serde_with = { workspace = true } diff --git a/crates/federate/Cargo.toml b/crates/federate/Cargo.toml index 5d7454276..bdfc00678 100644 --- a/crates/federate/Cargo.toml +++ b/crates/federate/Cargo.toml @@ -25,7 +25,7 @@ activitypub_federation.workspace = true anyhow.workspace = true futures.workspace = true chrono.workspace = true -diesel = { workspace = true, features = ["postgres", "chrono", "serde_json"] } +diesel = { workspace = true, features = ["chrono", "postgres", "serde_json"] } diesel-async = { workspace = true, features = ["deadpool", "postgres"] } reqwest.workspace = true serde_json.workspace = true diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 7ed4c0476..ded84132c 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -65,10 +65,10 @@ anyhow = { workspace = true, optional = true } reqwest-middleware = { workspace = true, optional = true } strum = { workspace = true } futures = { workspace = true, optional = true } -diesel = { workspace = true, features = ["chrono"], optional = true } +diesel = { workspace = true, optional = true, features = ["chrono"] } http = { workspace = true, optional = true } doku = { workspace = true, features = ["url-2"], optional = true } -uuid = { workspace = true, features = ["serde", "v4"], optional = true } +uuid = { workspace = true, optional = true, features = ["v4"] } rosetta-i18n = { workspace = true, optional = true } tokio = { workspace = true, optional = true } urlencoding = { workspace = true, optional = true } @@ -77,9 +77,8 @@ deser-hjson = { version = "2.2.4", optional = true } smart-default = { version = "0.7.1", optional = true } lettre = { version = "0.11.10", default-features = false, features = [ "builder", - "tokio1", - "tokio1-rustls-tls", "smtp-transport", + "tokio1-rustls-tls", ], optional = true } markdown-it = { version = "0.6.1", optional = true } ts-rs = { workspace = true, optional = true } From 7585aac446a060e0d8685e08fa552547ae9750de Mon Sep 17 00:00:00 2001 From: Nutomic Date: Sat, 21 Dec 2024 00:21:09 +0000 Subject: [PATCH 2/2] Fix startup errors, add unit test for scheduled task errors (fixes #5209) (#5269) * Fix startup errors, add ci check (fixes #5209) * normal unit test * cleanup * shear * remove serial * migration --- Cargo.lock | 1 - Cargo.toml | 1 - crates/db_schema/replaceable_schema/utils.sql | 36 +- .../down.sql | 3 + .../up.sql | 3 + src/lib.rs | 15 + src/scheduled_tasks.rs | 530 ++++++++---------- src/session_middleware.rs | 34 +- 8 files changed, 279 insertions(+), 344 deletions(-) create mode 100644 migrations/2024-12-20-090225_update-replaceable-schema/down.sql create mode 100644 migrations/2024-12-20-090225_update-replaceable-schema/up.sql diff --git a/Cargo.lock b/Cargo.lock index 10f629cd9..62f0bcd83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2805,7 +2805,6 @@ dependencies = [ "lemmy_utils", "pretty_assertions", "prometheus", - "reqwest 0.12.8", "reqwest-middleware", "reqwest-tracing", "rustls 0.23.16", diff --git a/Cargo.toml b/Cargo.toml index df10a78b8..b03a01715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,7 +182,6 @@ tracing = { workspace = true } tracing-actix-web = { workspace = true } tracing-subscriber = { workspace = true } url = { workspace = true } -reqwest = { workspace = true } reqwest-middleware = { workspace = true } reqwest-tracing = { workspace = true } clokwerk = { workspace = true } diff --git a/crates/db_schema/replaceable_schema/utils.sql b/crates/db_schema/replaceable_schema/utils.sql index 0c7f42ff2..a9b32f3dd 100644 --- a/crates/db_schema/replaceable_schema/utils.sql +++ b/crates/db_schema/replaceable_schema/utils.sql @@ -186,26 +186,26 @@ BEGIN AND pe.bot_account = FALSE UNION SELECT - pl.person_id, + pa.person_id, p.community_id FROM - post_like pl - INNER JOIN post p ON pl.post_id = p.id - INNER JOIN person pe ON pl.person_id = pe.id + post_actions pa + INNER JOIN post p ON pa.post_id = p.id + INNER JOIN person pe ON pa.person_id = pe.id WHERE - pl.published > ('now'::timestamp - i::interval) + pa.liked > ('now'::timestamp - i::interval) AND pe.bot_account = FALSE UNION SELECT - cl.person_id, + ca.person_id, p.community_id FROM - comment_like cl - INNER JOIN comment c ON cl.comment_id = c.id + comment_actions ca + INNER JOIN comment c ON ca.comment_id = c.id INNER JOIN post p ON c.post_id = p.id - INNER JOIN person pe ON cl.person_id = pe.id + INNER JOIN person pe ON ca.person_id = pe.id WHERE - cl.published > ('now'::timestamp - i::interval) + ca.liked > ('now'::timestamp - i::interval) AND pe.bot_account = FALSE) a GROUP BY community_id; @@ -244,22 +244,22 @@ BEGIN AND pe.bot_account = FALSE UNION SELECT - pl.person_id + pa.person_id FROM - post_like pl - INNER JOIN person pe ON pl.person_id = pe.id + post_actions pa + INNER JOIN person pe ON pa.person_id = pe.id WHERE - pl.published > ('now'::timestamp - i::interval) + pa.liked > ('now'::timestamp - i::interval) AND pe.local = TRUE AND pe.bot_account = FALSE UNION SELECT - cl.person_id + ca.person_id FROM - comment_like cl - INNER JOIN person pe ON cl.person_id = pe.id + comment_actions ca + INNER JOIN person pe ON ca.person_id = pe.id WHERE - cl.published > ('now'::timestamp - i::interval) + ca.liked > ('now'::timestamp - i::interval) AND pe.local = TRUE AND pe.bot_account = FALSE) a; RETURN count_; diff --git a/migrations/2024-12-20-090225_update-replaceable-schema/down.sql b/migrations/2024-12-20-090225_update-replaceable-schema/down.sql new file mode 100644 index 000000000..deb75def2 --- /dev/null +++ b/migrations/2024-12-20-090225_update-replaceable-schema/down.sql @@ -0,0 +1,3 @@ +SELECT + 1; + diff --git a/migrations/2024-12-20-090225_update-replaceable-schema/up.sql b/migrations/2024-12-20-090225_update-replaceable-schema/up.sql new file mode 100644 index 000000000..deb75def2 --- /dev/null +++ b/migrations/2024-12-20-090225_update-replaceable-schema/up.sql @@ -0,0 +1,3 @@ +SELECT + 1; + diff --git a/src/lib.rs b/src/lib.rs index 5586b6159..f344f2927 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -370,3 +370,18 @@ fn cors_config(settings: &Settings) -> Cors { _ => cors_default, } } + +#[cfg(test)] +pub mod tests { + use activitypub_federation::config::Data; + use lemmy_api_common::context::LemmyContext; + use std::env::set_current_dir; + + pub async fn test_context() -> Data { + // hack, necessary so that config file can be loaded from hardcoded, relative path. + // Ignore errors as this gets called once for every test (so changing dir again would fail). + set_current_dir("crates/utils").ok(); + + LemmyContext::init_test_context().await + } +} diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index 53c0b888b..b656ac36f 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -40,16 +40,19 @@ use lemmy_db_schema::{ utils::{find_action, functions::coalesce, get_conn, now, DbPool, DELETED_REPLACEMENT_TEXT}, }; use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; -use lemmy_utils::error::LemmyResult; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use reqwest_middleware::ClientWithMiddleware; use std::time::Duration; -use tracing::{error, info, warn}; +use tracing::{info, warn}; /// Schedules various cleanup tasks for lemmy in a background thread pub async fn setup(context: Data) -> LemmyResult<()> { // Setup the connections let mut scheduler = AsyncScheduler::new(); - startup_jobs(&mut context.pool()).await; + startup_jobs(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to run startup tasks: {e}")) + .ok(); let context_1 = context.clone(); // Update active counts expired bans and unpublished posts every hour @@ -57,9 +60,18 @@ pub async fn setup(context: Data) -> LemmyResult<()> { let context = context_1.clone(); async move { - active_counts(&mut context.pool()).await; - update_banned_when_expired(&mut context.pool()).await; - delete_instance_block_when_expired(&mut context.pool()).await; + active_counts(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to update active counts: {e}")) + .ok(); + update_banned_when_expired(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to update expired bans: {e}")) + .ok(); + delete_instance_block_when_expired(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to delete expired instance bans: {e}")) + .ok(); } }); @@ -69,9 +81,18 @@ pub async fn setup(context: Data) -> LemmyResult<()> { let context = context_1.reset_request_count(); async move { - update_hot_ranks(&mut context.pool()).await; - delete_expired_captcha_answers(&mut context.pool()).await; - publish_scheduled_posts(&context).await; + update_hot_ranks(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to update hot ranks: {e}")) + .ok(); + delete_expired_captcha_answers(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to delete expired captcha answers: {e}")) + .ok(); + publish_scheduled_posts(&context) + .await + .inspect_err(|e| warn!("Failed to publish scheduled posts: {e}")) + .ok(); } }); @@ -81,7 +102,10 @@ pub async fn setup(context: Data) -> LemmyResult<()> { let context = context_1.clone(); async move { - clear_old_activities(&mut context.pool()).await; + clear_old_activities(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to clear old activities: {e}")) + .ok(); } }); @@ -94,8 +118,14 @@ pub async fn setup(context: Data) -> LemmyResult<()> { let context = context_1.clone(); async move { - overwrite_deleted_posts_and_comments(&mut context.pool()).await; - delete_old_denied_users(&mut context.pool()).await; + overwrite_deleted_posts_and_comments(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to overwrite deleted posts/comments: {e}")) + .ok(); + delete_old_denied_users(&mut context.pool()) + .await + .inspect_err(|e| warn!("Failed to delete old denied users: {e}")) + .ok(); update_instance_software(&mut context.pool(), context.client()) .await .inspect_err(|e| warn!("Failed to update instance software: {e}")) @@ -111,49 +141,44 @@ pub async fn setup(context: Data) -> LemmyResult<()> { } /// Run these on server startup -async fn startup_jobs(pool: &mut DbPool<'_>) { - active_counts(pool).await; - update_hot_ranks(pool).await; - update_banned_when_expired(pool).await; - delete_instance_block_when_expired(pool).await; - clear_old_activities(pool).await; - overwrite_deleted_posts_and_comments(pool).await; - delete_old_denied_users(pool).await; +async fn startup_jobs(pool: &mut DbPool<'_>) -> LemmyResult<()> { + active_counts(pool).await?; + update_hot_ranks(pool).await?; + update_banned_when_expired(pool).await?; + delete_instance_block_when_expired(pool).await?; + clear_old_activities(pool).await?; + overwrite_deleted_posts_and_comments(pool).await?; + delete_old_denied_users(pool).await?; + Ok(()) } /// Update the hot_rank columns for the aggregates tables /// Runs in batches until all necessary rows are updated once -async fn update_hot_ranks(pool: &mut DbPool<'_>) { +async fn update_hot_ranks(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Updating hot ranks for all history..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - process_post_aggregates_ranks_in_batches(&mut conn).await; + process_post_aggregates_ranks_in_batches(&mut conn).await?; - process_ranks_in_batches( - &mut conn, - "comment", - "a.hot_rank != 0", - "SET hot_rank = r.hot_rank(a.score, a.published)", - ) - .await; + process_ranks_in_batches( + &mut conn, + "comment", + "a.hot_rank != 0", + "SET hot_rank = r.hot_rank(a.score, a.published)", + ) + .await?; - process_ranks_in_batches( - &mut conn, - "community", - "a.hot_rank != 0", - "SET hot_rank = r.hot_rank(a.subscribers, a.published)", - ) - .await; + process_ranks_in_batches( + &mut conn, + "community", + "a.hot_rank != 0", + "SET hot_rank = r.hot_rank(a.subscribers, a.published)", + ) + .await?; - info!("Finished hot ranks update!"); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + info!("Finished hot ranks update!"); + Ok(()) } #[derive(QueryableByName)] @@ -171,7 +196,7 @@ async fn process_ranks_in_batches( table_name: &str, where_clause: &str, set_clause: &str, -) { +) -> LemmyResult<()> { let process_start_time: DateTime = Utc.timestamp_opt(0, 0).single().unwrap_or_default(); let update_batch_size = 1000; // Bigger batches than this tend to cause seq scans @@ -180,7 +205,7 @@ async fn process_ranks_in_batches( while let Some(previous_batch_last_published) = previous_batch_result { // Raw `sql_query` is used as a performance optimization - Diesel does not support doing this // in a single query (neither as a CTE, nor using a subquery) - let result = sql_query(format!( + let updated_rows = sql_query(format!( r#"WITH batch AS (SELECT a.{id_column} FROM {aggregates_table} a WHERE a.published > $1 AND ({where_clause}) @@ -196,35 +221,31 @@ async fn process_ranks_in_batches( .bind::(previous_batch_last_published) .bind::(update_batch_size) .get_results::(conn) - .await; + .await + .map_err(|e| { + LemmyErrorType::Unknown(format!("Failed to update {} hot_ranks: {}", table_name, e)) + })?; - match result { - Ok(updated_rows) => { - processed_rows_count += updated_rows.len(); - previous_batch_result = updated_rows.last().map(|row| row.published); - } - Err(e) => { - error!("Failed to update {} hot_ranks: {}", table_name, e); - break; - } - } + processed_rows_count += updated_rows.len(); + previous_batch_result = updated_rows.last().map(|row| row.published); } info!( "Finished process_hot_ranks_in_batches execution for {} (processed {} rows)", table_name, processed_rows_count ); + Ok(()) } /// Post aggregates is a special case, since it needs to join to the community_aggregates /// table, to get the active monthly user counts. -async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection) { +async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection) -> LemmyResult<()> { let process_start_time: DateTime = Utc.timestamp_opt(0, 0).single().unwrap_or_default(); let update_batch_size = 1000; // Bigger batches than this tend to cause seq scans let mut processed_rows_count = 0; let mut previous_batch_result = Some(process_start_time); while let Some(previous_batch_last_published) = previous_batch_result { - let result = sql_query( + let updated_rows = sql_query( r#"WITH batch AS (SELECT pa.post_id FROM post_aggregates pa WHERE pa.published > $1 @@ -243,283 +264,190 @@ async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection) .bind::(previous_batch_last_published) .bind::(update_batch_size) .get_results::(conn) - .await; + .await.map_err(|e| LemmyErrorType::Unknown(format!("Failed to update {} hot_ranks: {}", "post_aggregates", e)))?; - match result { - Ok(updated_rows) => { - processed_rows_count += updated_rows.len(); - previous_batch_result = updated_rows.last().map(|row| row.published); - } - Err(e) => { - error!("Failed to update {} hot_ranks: {}", "post_aggregates", e); - break; - } - } + processed_rows_count += updated_rows.len(); + previous_batch_result = updated_rows.last().map(|row| row.published); } info!( "Finished process_hot_ranks_in_batches execution for {} (processed {} rows)", "post_aggregates", processed_rows_count ); + Ok(()) } -async fn delete_expired_captcha_answers(pool: &mut DbPool<'_>) { - let conn = get_conn(pool).await; +async fn delete_expired_captcha_answers(pool: &mut DbPool<'_>) -> LemmyResult<()> { + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - diesel::delete( - captcha_answer::table - .filter(captcha_answer::published.lt(now() - IntervalDsl::minutes(10))), - ) - .execute(&mut conn) - .await - .map(|_| { - info!("Done."); - }) - .inspect_err(|e| error!("Failed to clear old captcha answers: {e}")) - .ok(); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + diesel::delete( + captcha_answer::table.filter(captcha_answer::published.lt(now() - IntervalDsl::minutes(10))), + ) + .execute(&mut conn) + .await?; + info!("Done."); + + Ok(()) } /// Clear old activities (this table gets very large) -async fn clear_old_activities(pool: &mut DbPool<'_>) { +async fn clear_old_activities(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Clearing old activities..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - diesel::delete( - sent_activity::table.filter(sent_activity::published.lt(now() - IntervalDsl::days(7))), - ) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to clear old sent activities: {e}")) - .ok(); + diesel::delete( + sent_activity::table.filter(sent_activity::published.lt(now() - IntervalDsl::days(7))), + ) + .execute(&mut conn) + .await?; - diesel::delete( - received_activity::table - .filter(received_activity::published.lt(now() - IntervalDsl::days(7))), - ) - .execute(&mut conn) - .await - .map(|_| info!("Done.")) - .inspect_err(|e| error!("Failed to clear old received activities: {e}")) - .ok(); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + diesel::delete( + received_activity::table.filter(received_activity::published.lt(now() - IntervalDsl::days(7))), + ) + .execute(&mut conn) + .await?; + info!("Done."); + Ok(()) } -async fn delete_old_denied_users(pool: &mut DbPool<'_>) { - LocalUser::delete_old_denied_local_users(pool) - .await - .map(|_| { - info!("Done."); - }) - .inspect_err(|e| error!("Failed to deleted old denied users: {e}")) - .ok(); +async fn delete_old_denied_users(pool: &mut DbPool<'_>) -> LemmyResult<()> { + LocalUser::delete_old_denied_local_users(pool).await?; + info!("Done."); + Ok(()) } /// overwrite posts and comments 30d after deletion -async fn overwrite_deleted_posts_and_comments(pool: &mut DbPool<'_>) { +async fn overwrite_deleted_posts_and_comments(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Overwriting deleted posts..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - diesel::update( - post::table - .filter(post::deleted.eq(true)) - .filter(post::updated.lt(now().nullable() - 1.months())) - .filter(post::body.ne(DELETED_REPLACEMENT_TEXT)), - ) - .set(( - post::body.eq(DELETED_REPLACEMENT_TEXT), - post::name.eq(DELETED_REPLACEMENT_TEXT), - )) - .execute(&mut conn) - .await - .map(|_| { - info!("Done."); - }) - .inspect_err(|e| error!("Failed to overwrite deleted posts: {e}")) - .ok(); + diesel::update( + post::table + .filter(post::deleted.eq(true)) + .filter(post::updated.lt(now().nullable() - 1.months())) + .filter(post::body.ne(DELETED_REPLACEMENT_TEXT)), + ) + .set(( + post::body.eq(DELETED_REPLACEMENT_TEXT), + post::name.eq(DELETED_REPLACEMENT_TEXT), + )) + .execute(&mut conn) + .await?; - info!("Overwriting deleted comments..."); - diesel::update( - comment::table - .filter(comment::deleted.eq(true)) - .filter(comment::updated.lt(now().nullable() - 1.months())) - .filter(comment::content.ne(DELETED_REPLACEMENT_TEXT)), - ) - .set(comment::content.eq(DELETED_REPLACEMENT_TEXT)) - .execute(&mut conn) - .await - .map(|_| { - info!("Done."); - }) - .inspect_err(|e| error!("Failed to overwrite deleted comments: {e}")) - .ok(); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + info!("Overwriting deleted comments..."); + diesel::update( + comment::table + .filter(comment::deleted.eq(true)) + .filter(comment::updated.lt(now().nullable() - 1.months())) + .filter(comment::content.ne(DELETED_REPLACEMENT_TEXT)), + ) + .set(comment::content.eq(DELETED_REPLACEMENT_TEXT)) + .execute(&mut conn) + .await?; + info!("Done."); + Ok(()) } /// Re-calculate the site and community active counts every 12 hours -async fn active_counts(pool: &mut DbPool<'_>) { +async fn active_counts(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Updating active site and community aggregates ..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - let intervals = vec![ - ("1 day", "day"), - ("1 week", "week"), - ("1 month", "month"), - ("6 months", "half_year"), - ]; + let intervals = vec![ + ("1 day", "day"), + ("1 week", "week"), + ("1 month", "month"), + ("6 months", "half_year"), + ]; - for (full_form, abbr) in &intervals { - let update_site_stmt = format!( + for (full_form, abbr) in &intervals { + let update_site_stmt = format!( "update site_aggregates set users_active_{} = (select * from r.site_aggregates_activity('{}')) where site_id = 1", abbr, full_form ); - sql_query(update_site_stmt) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to update site stats: {e}")) - .ok(); + sql_query(update_site_stmt).execute(&mut conn).await?; - let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from r.community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form); - sql_query(update_community_stmt) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to update community stats: {e}")) - .ok(); - } - - info!("Done."); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } + let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from r.community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form); + sql_query(update_community_stmt).execute(&mut conn).await?; } + + info!("Done."); + Ok(()) } /// Set banned to false after ban expires -async fn update_banned_when_expired(pool: &mut DbPool<'_>) { +async fn update_banned_when_expired(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Updating banned column if it expires ..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - diesel::update( - person::table - .filter(person::banned.eq(true)) - .filter(person::ban_expires.lt(now().nullable())), - ) - .set(person::banned.eq(false)) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to update person.banned when expires: {e}")) - .ok(); + diesel::update( + person::table + .filter(person::banned.eq(true)) + .filter(person::ban_expires.lt(now().nullable())), + ) + .set(person::banned.eq(false)) + .execute(&mut conn) + .await?; - diesel::delete( - community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())), - ) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to remove community_ban expired rows: {e}")) - .ok(); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + diesel::delete( + community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())), + ) + .execute(&mut conn) + .await?; + Ok(()) } /// Set banned to false after ban expires -async fn delete_instance_block_when_expired(pool: &mut DbPool<'_>) { +async fn delete_instance_block_when_expired(pool: &mut DbPool<'_>) -> LemmyResult<()> { info!("Delete instance blocks when expired ..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - diesel::delete( - federation_blocklist::table.filter(federation_blocklist::expires.lt(now().nullable())), - ) - .execute(&mut conn) - .await - .inspect_err(|e| error!("Failed to remove federation_blocklist expired rows: {e}")) - .ok(); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } - } + diesel::delete( + federation_blocklist::table.filter(federation_blocklist::expires.lt(now().nullable())), + ) + .execute(&mut conn) + .await?; + Ok(()) } /// Find all unpublished posts with scheduled date in the future, and publish them. -async fn publish_scheduled_posts(context: &Data) { +async fn publish_scheduled_posts(context: &Data) -> LemmyResult<()> { let pool = &mut context.pool(); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - let scheduled_posts: Vec<_> = post::table - .inner_join(community::table) - .inner_join(person::table) - // find all posts which have scheduled_publish_time that is in the past - .filter(post::scheduled_publish_time.is_not_null()) - .filter(coalesce(post::scheduled_publish_time, now()).lt(now())) - // make sure the post, person and community are still around - .filter(not(post::deleted.or(post::removed))) - .filter(not(person::banned.or(person::deleted))) - .filter(not(community::removed.or(community::deleted))) - // ensure that user isnt banned from community - .filter(not(exists(find_action( - community_actions::received_ban, - (person::id, community::id), - )))) - .select((post::all_columns, community::all_columns)) - .get_results::<(Post, Community)>(&mut conn) - .await - .inspect_err(|e| error!("Failed to read unpublished posts: {e}")) - .ok() - .unwrap_or_default(); + let scheduled_posts: Vec<_> = post::table + .inner_join(community::table) + .inner_join(person::table) + // find all posts which have scheduled_publish_time that is in the past + .filter(post::scheduled_publish_time.is_not_null()) + .filter(coalesce(post::scheduled_publish_time, now()).lt(now())) + // make sure the post, person and community are still around + .filter(not(post::deleted.or(post::removed))) + .filter(not(person::banned.or(person::deleted))) + .filter(not(community::removed.or(community::deleted))) + // ensure that user isnt banned from community + .filter(not(exists(find_action( + community_actions::received_ban, + (person::id, community::id), + )))) + .select((post::all_columns, community::all_columns)) + .get_results::<(Post, Community)>(&mut conn) + .await?; - for (post, community) in scheduled_posts { - // mark post as published in db - let form = PostUpdateForm { - scheduled_publish_time: Some(None), - ..Default::default() - }; - Post::update(&mut context.pool(), post.id, &form) - .await - .inspect_err(|e| error!("Failed update scheduled post: {e}")) - .ok(); + for (post, community) in scheduled_posts { + // mark post as published in db + let form = PostUpdateForm { + scheduled_publish_time: Some(None), + ..Default::default() + }; + Post::update(&mut context.pool(), post.id, &form).await?; - // send out post via federation and webmention - let send_activity = SendActivityData::CreatePost(post.clone()); - ActivityChannel::submit_activity(send_activity, context) - .inspect_err(|e| error!("Failed federate scheduled post: {e}")) - .ok(); - send_webmention(post, community); - } - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); - } + // send out post via federation and webmention + let send_activity = SendActivityData::CreatePost(post.clone()); + ActivityChannel::submit_activity(send_activity, context)?; + send_webmention(post, community); } + Ok(()) } /// Updates the instance software and version. @@ -533,23 +461,16 @@ async fn update_instance_software( client: &ClientWithMiddleware, ) -> LemmyResult<()> { info!("Updating instances software and versions..."); - let conn = get_conn(pool).await; + let mut conn = get_conn(pool).await?; - match conn { - Ok(mut conn) => { - let instances = instance::table.get_results::(&mut conn).await?; + let instances = instance::table.get_results::(&mut conn).await?; - for instance in instances { - if let Some(form) = build_update_instance_form(&instance.domain, client).await { - Instance::update(pool, instance.id, form).await?; - } - } - info!("Finished updating instances software and versions..."); - } - Err(e) => { - error!("Failed to get connection from pool: {e}"); + for instance in instances { + if let Some(form) = build_update_instance_form(&instance.domain, client).await { + Instance::update(pool, instance.id, form).await?; } } + info!("Finished updating instances software and versions..."); Ok(()) } @@ -621,7 +542,8 @@ async fn build_update_instance_form( #[cfg(test)] mod tests { - use crate::scheduled_tasks::build_update_instance_form; + use super::*; + use crate::{scheduled_tasks::build_update_instance_form, tests::test_context}; use lemmy_api_common::request::client_builder; use lemmy_utils::{ error::{LemmyErrorType, LemmyResult}, @@ -632,7 +554,6 @@ mod tests { use serial_test::serial; #[tokio::test] - #[serial] async fn test_nodeinfo_lemmy_ml() -> LemmyResult<()> { let client = ClientBuilder::new(client_builder(&Settings::default()).build()?).build(); let form = build_update_instance_form("lemmy.ml", &client) @@ -643,7 +564,6 @@ mod tests { } #[tokio::test] - #[serial] async fn test_nodeinfo_mastodon_social() -> LemmyResult<()> { let client = ClientBuilder::new(client_builder(&Settings::default()).build()?).build(); let form = build_update_instance_form("mastodon.social", &client) @@ -652,4 +572,16 @@ mod tests { assert_eq!(form.software.ok_or(LemmyErrorType::NotFound)?, "mastodon"); Ok(()) } + + #[tokio::test] + #[serial] + async fn test_scheduled_tasks_no_errors() -> LemmyResult<()> { + let context = test_context().await; + + startup_jobs(&mut context.pool()).await?; + update_instance_software(&mut context.pool(), context.client()).await?; + delete_expired_captcha_answers(&mut context.pool()).await?; + publish_scheduled_posts(&context).await?; + Ok(()) + } } diff --git a/src/session_middleware.rs b/src/session_middleware.rs index b495bdbb9..7e0f38a4e 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -99,7 +99,7 @@ where #[cfg(test)] mod tests { - use super::*; + use crate::tests::test_context; use actix_web::test::TestRequest; use lemmy_api_common::claims::Claims; use lemmy_db_schema::{ @@ -107,45 +107,29 @@ mod tests { instance::Instance, local_user::{LocalUser, LocalUserInsertForm}, person::{Person, PersonInsertForm}, - secret::Secret, }, traits::Crud, - utils::build_db_pool_for_tests, }; - use lemmy_utils::{error::LemmyResult, rate_limit::RateLimitCell}; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; - use reqwest::Client; - use reqwest_middleware::ClientBuilder; use serial_test::serial; - use std::env::set_current_dir; #[tokio::test] #[serial] async fn test_session_auth() -> LemmyResult<()> { - // hack, necessary so that config file can be loaded from hardcoded, relative path - set_current_dir("crates/utils")?; + let context = test_context().await; - let pool_ = build_db_pool_for_tests(); - let pool = &mut (&pool_).into(); - - let secret = Secret::init(pool).await?; - - let context = LemmyContext::create( - pool_.clone(), - ClientBuilder::new(Client::default()).build(), - secret, - RateLimitCell::with_test_config(), - ); - - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + let inserted_instance = + Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812"); - let inserted_person = Person::create(pool, &new_person).await?; + let inserted_person = Person::create(&mut context.pool(), &new_person).await?; let local_user_form = LocalUserInsertForm::test_form(inserted_person.id); - let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; + let inserted_local_user = + LocalUser::create(&mut context.pool(), &local_user_form, vec![]).await?; let req = TestRequest::default().to_http_request(); let jwt = Claims::generate(inserted_local_user.id, req, &context).await?; @@ -153,7 +137,7 @@ mod tests { let valid = Claims::validate(&jwt, &context).await; assert!(valid.is_ok()); - let num_deleted = Person::delete(pool, inserted_person.id).await?; + let num_deleted = Person::delete(&mut context.pool(), inserted_person.id).await?; assert_eq!(1, num_deleted); Ok(())