From d476d322008116ddba25e2dee3f9e26b08734836 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 23 Sep 2024 20:55:35 -0400 Subject: [PATCH 1/7] Removing a few more Result . (#4977) * Removing a few more Result . * Running taplo fmt. * Running fmt. * Adding email taken test. * Fixing tests. * Adding back in missing admin check. * Rename check_has_local_followers function. --- api_tests/package.json | 10 +-- api_tests/pnpm-lock.yaml | 10 +-- api_tests/src/post.spec.ts | 2 +- crates/api/src/community/add_mod.rs | 5 +- crates/api/src/local_user/save_settings.rs | 4 +- crates/api_common/src/claims.rs | 8 +- crates/api_common/src/utils.rs | 84 ++----------------- crates/api_crud/src/post/create.rs | 5 +- crates/api_crud/src/private_message/create.rs | 8 +- crates/api_crud/src/user/create.rs | 29 +++---- .../apub/src/activities/community/announce.rs | 8 +- crates/apub/src/activities/mod.rs | 18 +--- crates/apub/src/objects/post.rs | 7 +- crates/apub/src/objects/private_message.rs | 5 +- crates/db_schema/src/impls/captcha_answer.rs | 11 +-- crates/db_schema/src/impls/community.rs | 13 +-- crates/db_schema/src/impls/community_block.rs | 15 ++-- crates/db_schema/src/impls/instance_block.rs | 15 ++-- crates/db_schema/src/impls/local_user.rs | 40 +++++++-- crates/db_schema/src/impls/login_token.rs | 9 +- crates/db_schema/src/impls/person_block.rs | 15 ++-- crates/db_views_actor/Cargo.toml | 9 +- .../src/community_moderator_view.rs | 15 ++-- .../src/community_person_ban_view.rs | 20 +++-- crates/db_views_actor/src/community_view.rs | 37 ++++---- 25 files changed, 182 insertions(+), 220 deletions(-) diff --git a/api_tests/package.json b/api_tests/package.json index cacd476ea..b06b84fbf 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -21,16 +21,16 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^22.0.2", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^9.8.0", + "@types/node": "^22.3.0", + "@typescript-eslint/eslint-plugin": "^8.1.0", + "@typescript-eslint/parser": "^8.1.0", + "eslint": "^9.9.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", "lemmy-js-client": "0.20.0-alpha.11", "prettier": "^3.2.5", "ts-jest": "^29.1.0", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.0" + "typescript-eslint": "^8.1.0" } } diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index f72450015..54c8a9d96 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -12,16 +12,16 @@ importers: specifier: ^29.5.12 version: 29.5.12 '@types/node': - specifier: ^22.0.2 + specifier: ^22.3.0 version: 22.3.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.0.0 + specifier: ^8.1.0 version: 8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4) '@typescript-eslint/parser': - specifier: ^8.0.0 + specifier: ^8.1.0 version: 8.1.0(eslint@9.9.0)(typescript@5.5.4) eslint: - specifier: ^9.8.0 + specifier: ^9.9.0 version: 9.9.0 eslint-plugin-prettier: specifier: ^5.1.3 @@ -42,7 +42,7 @@ importers: specifier: ^5.5.4 version: 5.5.4 typescript-eslint: - specifier: ^8.0.0 + specifier: ^8.1.0 version: 8.1.0(eslint@9.9.0)(typescript@5.5.4) packages: diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index bb0416057..ee9cc441d 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -628,7 +628,7 @@ test("Enforce community ban for federated user", async () => { // Alpha tries to make post on beta, but it fails because of ban await expect( createPost(alpha, betaCommunity.community.id), - ).rejects.toStrictEqual(Error("banned_from_community")); + ).rejects.toStrictEqual(Error("person_is_banned_from_community")); // Unban alpha let unBanAlpha = await banPersonFromCommunity( diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index 09e2c5b61..7d04f6bb0 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -52,15 +52,12 @@ pub async fn add_mod_to_community( // moderator. This is necessary because otherwise the action would be rejected // by the community's home instance. if local_user_view.local_user.admin && !community.local { - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community.id, local_user_view.person.id, ) .await?; - if !is_mod { - Err(LemmyErrorType::NotAModerator)? - } } // Update in local database diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index c62ab0e01..029914545 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -63,9 +63,7 @@ pub async fn save_user_settings( let previous_email = local_user_view.local_user.email.clone().unwrap_or_default(); // if email was changed, check that it is not taken and send verification mail if previous_email.deref() != email { - if LocalUser::is_email_taken(&mut context.pool(), email).await? { - return Err(LemmyErrorType::EmailAlreadyExists)?; - } + LocalUser::check_is_email_taken(&mut context.pool(), email).await?; send_verification_email( &local_user_view, email, diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index 905394785..06e62bae3 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -29,12 +29,8 @@ impl Claims { let claims = decode::(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?; let user_id = LocalUserId(claims.claims.sub.parse()?); - let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?; - if !is_valid { - Err(LemmyErrorType::NotLoggedIn)? - } else { - Ok(user_id) - } + LoginToken::validate(&mut context.pool(), user_id, jwt).await?; + Ok(user_id) } pub async fn generate( diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index db186247c..c34261511 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -73,13 +73,7 @@ pub async fn is_mod_or_admin( community_id: CommunityId, ) -> LemmyResult<()> { check_user_valid(person)?; - - let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?; - if !is_mod_or_admin { - Err(LemmyErrorType::NotAModOrAdmin)? - } else { - Ok(()) - } + CommunityView::check_is_mod_or_admin(pool, person.id, community_id).await } #[tracing::instrument(skip_all)] @@ -110,13 +104,7 @@ pub async fn check_community_mod_of_any_or_admin_action( let person = &local_user_view.person; check_user_valid(person)?; - - let is_mod_of_any_or_admin = CommunityView::is_mod_of_any_or_admin(pool, person.id).await?; - if !is_mod_of_any_or_admin { - Err(LemmyErrorType::NotAModOrAdmin)? - } else { - Ok(()) - } + CommunityView::check_is_mod_of_any_or_admin(pool, person.id).await } pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> { @@ -242,7 +230,7 @@ pub async fn check_community_user_action( ) -> LemmyResult<()> { check_user_valid(person)?; check_community_deleted_removed(community_id, pool).await?; - check_community_ban(person, community_id, pool).await?; + CommunityPersonBanView::check(pool, person.id, community_id).await?; Ok(()) } @@ -257,19 +245,6 @@ async fn check_community_deleted_removed( Ok(()) } -async fn check_community_ban( - person: &Person, - community_id: CommunityId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - // check if user was banned from site or community - let is_banned = CommunityPersonBanView::get(pool, person.id, community_id).await?; - if is_banned { - Err(LemmyErrorType::BannedFromCommunity)? - } - Ok(()) -} - /// Check that the given user can perform a mod action in the community. /// /// In particular it checks that he is an admin or mod, wasn't banned and the community isn't @@ -281,7 +256,7 @@ pub async fn check_community_mod_action( pool: &mut DbPool<'_>, ) -> LemmyResult<()> { is_mod_or_admin(pool, person, community_id).await?; - check_community_ban(person, community_id, pool).await?; + CommunityPersonBanView::check(pool, person.id, community_id).await?; // it must be possible to restore deleted community if !allow_deleted { @@ -307,51 +282,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> { } } -/// Throws an error if a recipient has blocked a person. -#[tracing::instrument(skip_all)] -pub async fn check_person_block( - my_id: PersonId, - potential_blocker_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?; - if is_blocked { - Err(LemmyErrorType::PersonIsBlocked)? - } else { - Ok(()) - } -} - -/// Throws an error if a recipient has blocked a community. -#[tracing::instrument(skip_all)] -async fn check_community_block( - community_id: CommunityId, - person_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?; - if is_blocked { - Err(LemmyErrorType::CommunityIsBlocked)? - } else { - Ok(()) - } -} - -/// Throws an error if a recipient has blocked an instance. -#[tracing::instrument(skip_all)] -async fn check_instance_block( - instance_id: InstanceId, - person_id: PersonId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?; - if is_blocked { - Err(LemmyErrorType::InstanceIsBlocked)? - } else { - Ok(()) - } -} - #[tracing::instrument(skip_all)] pub async fn check_person_instance_community_block( my_id: PersonId, @@ -360,9 +290,9 @@ pub async fn check_person_instance_community_block( community_id: CommunityId, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { - check_person_block(my_id, potential_blocker_id, pool).await?; - check_instance_block(community_instance_id, potential_blocker_id, pool).await?; - check_community_block(community_id, potential_blocker_id, pool).await?; + PersonBlock::read(pool, potential_blocker_id, my_id).await?; + InstanceBlock::read(pool, potential_blocker_id, community_instance_id).await?; + CommunityBlock::read(pool, potential_blocker_id, community_id).await?; Ok(()) } diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 95b4e4722..cdeb10f44 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -95,15 +95,12 @@ pub async fn create_post( let community = Community::read(&mut context.pool(), community_id).await?; if community.posting_restricted_to_mods { let community_id = data.community_id; - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community_id, local_user_view.local_user.person_id, ) .await?; - if !is_mod { - Err(LemmyErrorType::OnlyModsCanPostInCommunity)? - } } // Only need to check if language is allowed in case user set it explicitly. When using default diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index e79241022..2a49e4ac0 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -5,7 +5,6 @@ use lemmy_api_common::{ private_message::{CreatePrivateMessage, PrivateMessageResponse}, send_activity::{ActivityChannel, SendActivityData}, utils::{ - check_person_block, get_interface_language, get_url_blocklist, local_site_to_slur_regex, @@ -16,6 +15,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{ source::{ local_site::LocalSite, + person_block::PersonBlock, private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, @@ -39,10 +39,10 @@ pub async fn create_private_message( let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; is_valid_body_field(&content, false)?; - check_person_block( - local_user_view.person.id, - data.recipient_id, + PersonBlock::read( &mut context.pool(), + data.recipient_id, + local_user_view.person.id, ) .await?; diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index 174b02930..ec859cd49 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -92,22 +92,15 @@ pub async fn register( } if local_site.site_setup && local_site.captcha_enabled { - if let Some(captcha_uuid) = &data.captcha_uuid { - let uuid = uuid::Uuid::parse_str(captcha_uuid)?; - let check = CaptchaAnswer::check_captcha( - &mut context.pool(), - CheckCaptchaAnswer { - uuid, - answer: data.captcha_answer.clone().unwrap_or_default(), - }, - ) - .await?; - if !check { - Err(LemmyErrorType::CaptchaIncorrect)? - } - } else { - Err(LemmyErrorType::CaptchaIncorrect)? - } + let uuid = uuid::Uuid::parse_str(&data.captcha_uuid.clone().unwrap_or_default())?; + CaptchaAnswer::check_captcha( + &mut context.pool(), + CheckCaptchaAnswer { + uuid, + answer: data.captcha_answer.clone().unwrap_or_default(), + }, + ) + .await?; } let slur_regex = local_site_to_slur_regex(&local_site); @@ -119,9 +112,7 @@ pub async fn register( } if let Some(email) = &data.email { - if LocalUser::is_email_taken(&mut context.pool(), email).await? { - Err(LemmyErrorType::EmailAlreadyExists)? - } + LocalUser::check_is_email_taken(&mut context.pool(), email).await?; } // We have to create both a person, and local_user diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 69dbbdd3d..0a506a791 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -213,15 +213,13 @@ async fn can_accept_activity_in_community( context: &Data, ) -> LemmyResult<()> { if let Some(community) = community { - if !community.local - && !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await? - { - Err(LemmyErrorType::CommunityHasNoFollowers)? - } // Local only community can't federate if community.visibility != CommunityVisibility::Public { return Err(LemmyErrorType::NotFound.into()); } + if !community.local { + CommunityFollower::check_has_local_followers(&mut context.pool(), community.id).await? + } } Ok(()) } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 5389e5fdd..2c371f71c 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -87,12 +87,7 @@ pub(crate) async fn verify_person_in_community( } let person_id = person.id; let community_id = community.id; - let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id).await?; - if is_banned { - Err(LemmyErrorType::PersonIsBannedFromCommunity)? - } else { - Ok(()) - } + CommunityPersonBanView::check(&mut context.pool(), person_id, community_id).await } /// Verify that mod action in community was performed by a moderator. @@ -106,14 +101,6 @@ pub(crate) async fn verify_mod_action( community: &Community, context: &Data, ) -> LemmyResult<()> { - let mod_ = mod_id.dereference(context).await?; - - let is_mod_or_admin = - CommunityView::is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await?; - if is_mod_or_admin { - return Ok(()); - } - // mod action comes from the same instance as the community, so it was presumably done // by an instance admin. // TODO: federate instance admin status and check it here @@ -121,7 +108,8 @@ pub(crate) async fn verify_mod_action( return Ok(()); } - Err(LemmyErrorType::NotAModerator)? + let mod_ = mod_id.dereference(context).await?; + CommunityView::check_is_mod_or_admin(&mut context.pool(), mod_.id, community.id).await } pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index dd8b247d7..dfc9d79f9 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -39,7 +39,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyResult}, spawn_try_task, utils::{ markdown::markdown_to_html, @@ -180,15 +180,12 @@ impl Object for ApubPost { let creator = page.creator()?.dereference(context).await?; let community = page.community(context).await?; if community.posting_restricted_to_mods { - let is_mod = CommunityModeratorView::is_community_moderator( + CommunityModeratorView::check_is_community_moderator( &mut context.pool(), community.id, creator.id, ) .await?; - if !is_mod { - Err(LemmyErrorType::OnlyModsCanPostInCommunity)? - } } let mut name = page .name diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index ccebcf110..d3ca340db 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -15,12 +15,13 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{check_person_block, get_url_blocklist, local_site_opt_to_slur_regex, process_markdown}, + utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown}, }; use lemmy_db_schema::{ source::{ local_site::LocalSite, person::Person, + person_block::PersonBlock, private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, @@ -126,7 +127,7 @@ impl Object for ApubPrivateMessage { ) -> LemmyResult { let creator = note.attributed_to.dereference(context).await?; let recipient = note.to[0].dereference(context).await?; - check_person_block(creator.id, recipient.id, &mut context.pool()).await?; + PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?; let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); diff --git a/crates/db_schema/src/impls/captcha_answer.rs b/crates/db_schema/src/impls/captcha_answer.rs index 1d0604c0a..976d89515 100644 --- a/crates/db_schema/src/impls/captcha_answer.rs +++ b/crates/db_schema/src/impls/captcha_answer.rs @@ -13,6 +13,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CaptchaAnswer { pub async fn insert(pool: &mut DbPool<'_>, captcha: &CaptchaAnswerForm) -> Result { @@ -27,7 +28,7 @@ impl CaptchaAnswer { pub async fn check_captcha( pool: &mut DbPool<'_>, to_check: CheckCaptchaAnswer, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; // fetch requested captcha @@ -43,7 +44,9 @@ impl CaptchaAnswer { .execute(conn) .await?; - Ok(captcha_exists) + captcha_exists + .then_some(()) + .ok_or(LemmyErrorType::CaptchaIncorrect.into()) } } @@ -83,7 +86,6 @@ mod tests { .await; assert!(result.is_ok()); - assert!(result.unwrap()); } #[tokio::test] @@ -119,7 +121,6 @@ mod tests { ) .await; - assert!(result_repeat.is_ok()); - assert!(!result_repeat.unwrap()); + assert!(result_repeat.is_err()); } } diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index a26fdc233..f106ca424 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -35,8 +35,7 @@ use crate::{ use chrono::{DateTime, Utc}; use diesel::{ deserialize, - dsl, - dsl::{exists, insert_into}, + dsl::{self, exists, insert_into}, pg::Pg, result::Error, select, @@ -320,16 +319,18 @@ impl CommunityFollower { /// Check if a remote instance has any followers on local instance. For this it is enough to check /// if any follow relation is stored. Dont use this for local community. - pub async fn has_local_followers( + pub async fn check_has_local_followers( pool: &mut DbPool<'_>, remote_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; select(exists(community_follower::table.filter( community_follower::community_id.eq(remote_community_id), ))) - .get_result(conn) - .await + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityHasNoFollowers.into()) } } diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 944a53ae3..cd541cd8b 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -9,26 +9,29 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( community_block::table.find((for_person_id, for_community_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 15fcd1443..1eb6e8f04 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -9,26 +9,29 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl InstanceBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_instance_id: InstanceId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( instance_block::table.find((for_person_id, for_instance_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::InstanceIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 2330a3864..a06f22e34 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -136,14 +136,16 @@ impl LocalUser { diesel::delete(persons).execute(conn).await } - pub async fn is_email_taken(pool: &mut DbPool<'_>, email: &str) -> Result { + pub async fn check_is_email_taken(pool: &mut DbPool<'_>, email: &str) -> LemmyResult<()> { use diesel::dsl::{exists, select}; let conn = &mut get_conn(pool).await?; - select(exists(local_user::table.filter( + select(not(exists(local_user::table.filter( lower(coalesce(local_user::email, "")).eq(email.to_lowercase()), - ))) - .get_result(conn) - .await + )))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::EmailAlreadyExists.into()) } // TODO: maybe move this and pass in LocalUserView @@ -419,4 +421,32 @@ mod tests { Ok(()) } + + #[tokio::test] + #[serial] + async fn test_email_taken() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let darwin_email = "charles.darwin@gmail.com"; + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + + let darwin_person = PersonInsertForm::test_form(inserted_instance.id, "darwin"); + let inserted_darwin_person = Person::create(pool, &darwin_person).await?; + + let mut darwin_local_user_form = + LocalUserInsertForm::test_form_admin(inserted_darwin_person.id); + darwin_local_user_form.email = Some(darwin_email.into()); + let _inserted_darwin_local_user = + LocalUser::create(pool, &darwin_local_user_form, vec![]).await?; + + let check = LocalUser::check_is_email_taken(pool, darwin_email).await; + assert!(check.is_err()); + + let passed_check = LocalUser::check_is_email_taken(pool, "not_charles@gmail.com").await; + assert!(passed_check.is_ok()); + + Ok(()) + } } diff --git a/crates/db_schema/src/impls/login_token.rs b/crates/db_schema/src/impls/login_token.rs index 71cac6a19..c8c44c506 100644 --- a/crates/db_schema/src/impls/login_token.rs +++ b/crates/db_schema/src/impls/login_token.rs @@ -7,6 +7,7 @@ use crate::{ }; use diesel::{delete, dsl::exists, insert_into, result::Error, select}; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl LoginToken { pub async fn create(pool: &mut DbPool<'_>, form: LoginTokenCreateForm) -> Result { @@ -22,13 +23,15 @@ impl LoginToken { pool: &mut DbPool<'_>, user_id_: LocalUserId, token_: &str, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; select(exists( login_token.find(token_).filter(user_id.eq(user_id_)), )) - .get_result(conn) - .await + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotLoggedIn.into()) } pub async fn list( diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index cfd41f6d6..7f2286616 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -9,7 +9,7 @@ use crate::{ utils::{get_conn, DbPool}, }; use diesel::{ - dsl::{exists, insert_into}, + dsl::{exists, insert_into, not}, result::Error, select, ExpressionMethods, @@ -17,19 +17,22 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl PersonBlock { pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_recipient_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( person_block::table.find((for_person_id, for_recipient_id)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBlocked.into()) } pub async fn for_person( diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index af139b8b2..c29b7d66d 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -15,7 +15,13 @@ doctest = false workspace = true [features] -full = ["lemmy_db_schema/full", "diesel", "diesel-async", "ts-rs"] +full = [ + "lemmy_db_schema/full", + "lemmy_utils/full", + "diesel", + "diesel-async", + "ts-rs", +] [dependencies] lemmy_db_schema = { workspace = true } @@ -33,6 +39,7 @@ serde_with = { workspace = true } ts-rs = { workspace = true, optional = true } chrono.workspace = true strum = { workspace = true } +lemmy_utils = { workspace = true, optional = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index f2a59fd9f..ebcdcbd25 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -8,13 +8,14 @@ use lemmy_db_schema::{ source::local_user::LocalUser, utils::{get_conn, DbPool}, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityModeratorView { - pub async fn is_community_moderator( + pub async fn check_is_community_moderator( pool: &mut DbPool<'_>, find_community_id: CommunityId, find_person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { use lemmy_db_schema::schema::community_moderator::dsl::{ community_id, community_moderator, @@ -27,20 +28,24 @@ impl CommunityModeratorView { .filter(person_id.eq(find_person_id)), )) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotAModerator.into()) } pub(crate) async fn is_community_moderator_of_any( pool: &mut DbPool<'_>, find_person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id}; let conn = &mut get_conn(pool).await?; select(exists( community_moderator.filter(person_id.eq(find_person_id)), )) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotAModerator.into()) } pub async fn for_community( diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index 712bb2d3a..5543222f3 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -1,25 +1,33 @@ use crate::structs::CommunityPersonBanView; -use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; +use diesel::{ + dsl::{exists, not}, + select, + ExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::community_person_ban, utils::{get_conn, DbPool}, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; impl CommunityPersonBanView { - pub async fn get( + pub async fn check( pool: &mut DbPool<'_>, from_person_id: PersonId, from_community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( community_person_ban::table .filter(community_person_ban::community_id.eq(from_community_id)) .filter(community_person_ban::person_id.eq(from_person_id)), - )) + ))) .get_result::(conn) - .await + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into()) } } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 6282bef31..a1b387bea 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -26,6 +26,7 @@ use lemmy_db_schema::{ ListingType, PostSortType, }; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; fn queries<'a>() -> Queries< impl ReadFn<'a, CommunityView, (CommunityId, Option<&'a LocalUser>, bool)>, @@ -185,35 +186,39 @@ impl CommunityView { .await } - pub async fn is_mod_or_admin( + pub async fn check_is_mod_or_admin( pool: &mut DbPool<'_>, person_id: PersonId, community_id: CommunityId, - ) -> Result { + ) -> LemmyResult<()> { let is_mod = - CommunityModeratorView::is_community_moderator(pool, community_id, person_id).await?; - if is_mod { - Ok(true) - } else if let Ok(person_view) = PersonView::read(pool, person_id).await { - Ok(person_view.is_admin) + CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await; + if is_mod.is_ok() + || PersonView::read(pool, person_id) + .await + .is_ok_and(|t| t.is_admin) + { + Ok(()) } else { - Ok(false) + Err(LemmyErrorType::NotAModOrAdmin)? } } /// Checks if a person is an admin, or moderator of any community. - pub async fn is_mod_of_any_or_admin( + pub async fn check_is_mod_of_any_or_admin( pool: &mut DbPool<'_>, person_id: PersonId, - ) -> Result { + ) -> LemmyResult<()> { let is_mod_of_any = - CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await?; - if is_mod_of_any { - Ok(true) - } else if let Ok(person_view) = PersonView::read(pool, person_id).await { - Ok(person_view.is_admin) + CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await; + if is_mod_of_any.is_ok() + || PersonView::read(pool, person_id) + .await + .is_ok_and(|t| t.is_admin) + { + Ok(()) } else { - Ok(false) + Err(LemmyErrorType::NotAModOrAdmin)? } } } From bab5c9306274d7590940d4d30d5f268534778b52 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 24 Sep 2024 10:33:53 +0200 Subject: [PATCH 2/7] Conditionally hide comments on nsfw posts (fixes #4237) (#5028) * Conditionally hide comments on nsfw posts (fixes #4237) * fix test --- crates/api_common/src/utils.rs | 3 +- crates/apub/src/api/list_comments.rs | 17 +++--- crates/apub/src/api/read_person.rs | 2 +- crates/apub/src/api/search.rs | 8 ++- crates/db_views/src/comment_view.rs | 90 +++++++++++++++++++++------- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index c34261511..06c37ad16 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -776,12 +776,13 @@ pub async fn remove_or_restore_user_data_in_community( // Comments // TODO Diesel doesn't allow updates with joins, so this has to be a loop + let site = Site::read_local(pool).await?; let comments = CommentQuery { creator_id: Some(banned_person_id), community_id: Some(community_id), ..Default::default() } - .list(pool) + .list(&site, pool) .await?; for comment_view in &comments { diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index c97e051d0..3e7a2f4eb 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -12,10 +12,13 @@ use lemmy_api_common::{ utils::check_private_instance, }; use lemmy_db_schema::{ - source::{comment::Comment, community::Community, local_site::LocalSite}, + source::{comment::Comment, community::Community}, traits::Crud, }; -use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView}; +use lemmy_db_views::{ + comment_view::CommentQuery, + structs::{LocalUserView, SiteView}, +}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] @@ -24,8 +27,8 @@ pub async fn list_comments( context: Data, local_user_view: Option, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; - check_private_instance(&local_user_view, &local_site)?; + let site_view = SiteView::read_local(&mut context.pool()).await?; + check_private_instance(&local_user_view, &site_view.local_site)?; let community_id = if let Some(name) = &data.community_name { Some( @@ -40,7 +43,7 @@ pub async fn list_comments( let sort = Some(comment_sort_type_with_default( data.sort, local_user_ref, - &local_site, + &site_view.local_site, )); let max_depth = data.max_depth; let saved_only = data.saved_only; @@ -58,7 +61,7 @@ pub async fn list_comments( let listing_type = Some(listing_type_with_default( data.type_, local_user_view.as_ref().map(|u| &u.local_user), - &local_site, + &site_view.local_site, community_id, )); @@ -88,7 +91,7 @@ pub async fn list_comments( limit, ..Default::default() } - .list(&mut context.pool()) + .list(&site_view.site, &mut context.pool()) .await .with_lemmy_type(LemmyErrorType::CouldntGetComments)?; diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index 58e68f4b9..fac68cd63 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -85,7 +85,7 @@ pub async fn read_person( creator_id, ..Default::default() } - .list(&mut context.pool()) + .list(&local_site.site, &mut context.pool()) .await?; let moderates = CommunityModeratorView::for_person( diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 492d2b087..642c32efa 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -127,7 +127,9 @@ pub async fn search( .await?; } SearchType::Comments => { - comments = comment_query.list(&mut context.pool()).await?; + comments = comment_query + .list(&local_site.site, &mut context.pool()) + .await?; } SearchType::Communities => { communities = community_query @@ -146,7 +148,9 @@ pub async fn search( .list(&local_site.site, &mut context.pool()) .await?; - comments = comment_query.list(&mut context.pool()).await?; + comments = comment_query + .list(&local_site.site, &mut context.pool()) + .await?; communities = if community_or_creator_included { vec![] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 4f6135479..494536c87 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -35,7 +35,7 @@ use lemmy_db_schema::{ person_block, post, }, - source::local_user::LocalUser, + source::{local_user::LocalUser, site::Site}, utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, CommentSortType, ListingType, @@ -43,7 +43,7 @@ use lemmy_db_schema::{ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentView, (CommentId, Option<&'a LocalUser>)>, - impl ListFn<'a, CommentView, CommentQuery<'a>>, + impl ListFn<'a, CommentView, (CommentQuery<'a>, &'a Site)>, > { let is_creator_banned_from_community = exists( community_person_ban::table.filter( @@ -182,7 +182,7 @@ fn queries<'a>() -> Queries< query.first(&mut conn).await }; - let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move { + let list = move |mut conn: DbConn<'a>, (options, site): (CommentQuery<'a>, &'a Site)| async move { // The left join below will return None in this case let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1)); let local_user_id_join = options @@ -295,6 +295,12 @@ fn queries<'a>() -> Queries< query = query.filter(not(is_creator_blocked(person_id_join))); }; + if !options.local_user.show_nsfw(site) { + query = query + .filter(post::nsfw.eq(false)) + .filter(community::nsfw.eq(false)); + }; + query = options.local_user.visible_communities_only(query); // A Max depth given means its a tree fetch @@ -398,10 +404,10 @@ pub struct CommentQuery<'a> { } impl<'a> CommentQuery<'a> { - pub async fn list(self, pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { Ok( queries() - .list(pool, self) + .list(pool, (self, site)) .await? .into_iter() .map(|mut c| { @@ -455,7 +461,8 @@ mod tests { local_user_vote_display_mode::LocalUserVoteDisplayMode, person::{Person, PersonInsertForm}, person_block::{PersonBlock, PersonBlockForm}, - post::{Post, PostInsertForm}, + post::{Post, PostInsertForm, PostUpdateForm}, + site::{Site, SiteInsertForm}, }, traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable}, utils::{build_db_pool_for_tests, RANK_DEFAULT}, @@ -475,6 +482,7 @@ mod tests { timmy_local_user_view: LocalUserView, inserted_sara_person: Person, inserted_community: Community, + site: Site, } async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { @@ -611,6 +619,11 @@ mod tests { person: inserted_timmy_person.clone(), counts: Default::default(), }; + let site_form = SiteInsertForm::builder() + .name("test site".to_string()) + .instance_id(inserted_instance.id) + .build(); + let site = Site::create(pool, &site_form).await?; Ok(Data { inserted_instance, inserted_comment_0, @@ -620,6 +633,7 @@ mod tests { timmy_local_user_view, inserted_sara_person, inserted_community, + site, }) } @@ -640,7 +654,7 @@ mod tests { post_id: (Some(data.inserted_post.id)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!( @@ -654,7 +668,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!( @@ -706,7 +720,7 @@ mod tests { liked_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await? .into_iter() .map(|c| c.comment.content) @@ -722,7 +736,7 @@ mod tests { disliked_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert!(read_disliked_comment_views.is_empty()); @@ -743,7 +757,7 @@ mod tests { parent_path: (Some(top_path)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; let child_path = data.inserted_comment_1.path.clone(); @@ -752,7 +766,7 @@ mod tests { parent_path: (Some(child_path)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure the comment parent-limited fetch is correct @@ -772,7 +786,7 @@ mod tests { max_depth: (Some(1)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure a depth limited one only has the top comment @@ -790,7 +804,7 @@ mod tests { sort: (Some(CommentSortType::New)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Make sure a depth limited one, and given child comment 1, has 3 @@ -816,7 +830,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(5, all_languages); @@ -834,7 +848,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(2, finnish_comments); let finnish_comment = finnish_comments @@ -857,7 +871,7 @@ mod tests { local_user: (Some(&data.timmy_local_user_view.local_user)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_length!(1, undetermined_comment); @@ -881,7 +895,7 @@ mod tests { post_id: Some(data.inserted_comment_2.post_id), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(comments[0].comment.id, data.inserted_comment_2.id); assert!(comments[0].comment.distinguished); @@ -910,7 +924,7 @@ mod tests { sort: (Some(CommentSortType::Old)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(comments[1].creator.name, "sara"); @@ -931,7 +945,7 @@ mod tests { sort: (Some(CommentSortType::Old)), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // Timmy is an admin, and make sure that field is true @@ -971,7 +985,7 @@ mod tests { saved_only: Some(true), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; // There should only be two comments @@ -1001,6 +1015,7 @@ mod tests { LocalUser::delete(pool, data.timmy_local_user_view.local_user.id).await?; Person::delete(pool, data.inserted_sara_person.id).await?; Instance::delete(pool, data.inserted_instance.id).await?; + Site::delete(pool, data.site.id).await?; Ok(()) } @@ -1139,7 +1154,7 @@ mod tests { let unauthenticated_query = CommentQuery { ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(0, unauthenticated_query.len()); @@ -1147,7 +1162,7 @@ mod tests { local_user: Some(&data.timmy_local_user_view.local_user), ..Default::default() } - .list(pool) + .list(&data.site, pool) .await?; assert_eq!(5, authenticated_query.len()); @@ -1225,4 +1240,33 @@ mod tests { cleanup(data, pool).await } + + #[tokio::test] + #[serial] + async fn comment_listings_hide_nsfw() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Mark a post as nsfw + let update_form = PostUpdateForm { + nsfw: Some(true), + ..Default::default() + }; + Post::update(pool, data.inserted_post.id, &update_form).await?; + + // Make sure comments of this post are not returned + let comments = CommentQuery::default().list(&data.site, pool).await?; + assert_eq!(0, comments.len()); + + // Mark site as nsfw + let mut site = data.site.clone(); + site.content_warning = Some("nsfw".to_string()); + + // Now comments of nsfw post are returned + let comments = CommentQuery::default().list(&site, pool).await?; + assert_eq!(6, comments.len()); + + cleanup(data, pool).await + } } From 9eee61dd06220176fbb97ccbba4a594ea21bb5c6 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 24 Sep 2024 11:39:40 +0200 Subject: [PATCH 3/7] Post scheduling (fixes #234) (#5025) * Post scheduling (fixes #234) * clippy * replace map_err with inspect_err * ignore unpublished posts in read queries * add api test * fmt * add some checks * address some review comments * allow updating schedule time * rewrite scheduled task * fmt * machete * compare date in sql, more filters * check for community ban in sql * remove api test (scheduled task only runs every 10 mins) * remove mut * add index * remove Post::read impl * fmt * fix * correctly handle changes to schedule time * normal users can only schedule up to 10 posts --- Cargo.lock | 1 + crates/api_common/src/post.rs | 4 + crates/api_crud/Cargo.toml | 1 + crates/api_crud/src/post/create.rs | 23 +++- crates/api_crud/src/post/mod.rs | 33 +++++ crates/api_crud/src/post/update.rs | 55 +++++++-- crates/db_schema/src/impls/post.rs | 31 ++++- crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/source/post.rs | 5 + crates/db_views/src/comment_view.rs | 6 +- crates/db_views/src/post_view.rs | 12 +- crates/utils/src/error.rs | 2 + .../2024-09-16-095656_schedule-post/down.sql | 3 + .../2024-09-16-095656_schedule-post/up.sql | 5 + src/lib.rs | 14 ++- src/scheduled_tasks.rs | 114 +++++++++++++----- 16 files changed, 254 insertions(+), 56 deletions(-) create mode 100644 migrations/2024-09-16-095656_schedule-post/down.sql create mode 100644 migrations/2024-09-16-095656_schedule-post/up.sql diff --git a/Cargo.lock b/Cargo.lock index 135934193..a799d9d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2538,6 +2538,7 @@ dependencies = [ "actix-web", "anyhow", "bcrypt", + "chrono", "futures", "lemmy_api_common", "lemmy_db_schema", diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 44436fa84..fa45459e2 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -30,6 +30,8 @@ pub struct CreatePost { pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. pub custom_thumbnail: Option, + /// Time when this post should be scheduled. Null means publish immediately. + pub scheduled_publish_time: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -124,6 +126,8 @@ pub struct EditPost { pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. pub custom_thumbnail: Option, + /// Time when this post should be scheduled. Null means publish immediately. + pub scheduled_publish_time: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml index 259116a38..2793beac3 100644 --- a/crates/api_crud/Cargo.toml +++ b/crates/api_crud/Cargo.toml @@ -27,6 +27,7 @@ futures.workspace = true uuid = { workspace = true } moka.workspace = true anyhow.workspace = true +chrono.workspace = true webmention = "0.6.0" accept-language = "3.1.0" serde_json = { workspace = true } diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index cdeb10f44..a1357395b 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -1,3 +1,4 @@ +use super::convert_published_time; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -125,12 +126,15 @@ pub async fn create_post( } }; + let scheduled_publish_time = + convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?; let post_form = PostInsertForm { url: url.map(Into::into), body, alt_text: data.alt_text.clone(), nsfw: data.nsfw, language_id, + scheduled_publish_time, ..PostInsertForm::new( data.name.trim().to_string(), local_user_view.person.id, @@ -142,10 +146,16 @@ pub async fn create_post( .await .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; + let federate_post = if scheduled_publish_time.is_none() { + send_webmention(inserted_post.clone(), community); + |post| Some(SendActivityData::CreatePost(post)) + } else { + |_| None + }; generate_post_link_metadata( inserted_post.clone(), custom_thumbnail.map(Into::into), - |post| Some(SendActivityData::CreatePost(post)), + federate_post, context.reset_request_count(), ) .await?; @@ -165,11 +175,14 @@ pub async fn create_post( mark_post_as_read(person_id, post_id, &mut context.pool()).await?; - if let Some(url) = inserted_post.url.clone() { + build_post_response(&context, community_id, local_user_view, post_id).await +} + +pub fn send_webmention(post: Post, community: Community) { + if let Some(url) = post.url.clone() { if community.visibility == CommunityVisibility::Public { spawn_try_task(async move { - let mut webmention = - Webmention::new::(inserted_post.ap_id.clone().into(), url.clone().into())?; + let mut webmention = Webmention::new::(post.ap_id.clone().into(), url.clone().into())?; webmention.set_checked(true); match webmention .send() @@ -183,6 +196,4 @@ pub async fn create_post( }); } }; - - build_post_response(&context, community_id, local_user_view, post_id).await } diff --git a/crates/api_crud/src/post/mod.rs b/crates/api_crud/src/post/mod.rs index 8bb842b70..95df9663c 100644 --- a/crates/api_crud/src/post/mod.rs +++ b/crates/api_crud/src/post/mod.rs @@ -1,5 +1,38 @@ +use chrono::{DateTime, TimeZone, Utc}; +use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::source::post::Post; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + pub mod create; pub mod delete; pub mod read; pub mod remove; pub mod update; + +async fn convert_published_time( + scheduled_publish_time: Option, + local_user_view: &LocalUserView, + context: &LemmyContext, +) -> LemmyResult>> { + const MAX_SCHEDULED_POSTS: i64 = 10; + if let Some(scheduled_publish_time) = scheduled_publish_time { + let converted = Utc + .timestamp_opt(scheduled_publish_time, 0) + .single() + .ok_or(LemmyErrorType::InvalidUnixTime)?; + if converted < Utc::now() { + Err(LemmyErrorType::PostScheduleTimeMustBeInFuture)?; + } + if !local_user_view.local_user.admin { + let count = + Post::user_scheduled_post_count(local_user_view.person.id, &mut context.pool()).await?; + if count >= MAX_SCHEDULED_POSTS { + Err(LemmyErrorType::TooManyScheduledPosts)?; + } + } + Ok(Some(converted)) + } else { + Ok(None) + } +} diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 48b2ccd42..72f8309d1 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -1,3 +1,4 @@ +use super::{convert_published_time, create::send_webmention}; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -16,6 +17,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{ source::{ actor_language::CommunityLanguage, + community::Community, local_site::LocalSite, post::{Post, PostUpdateForm}, }, @@ -107,6 +109,21 @@ pub async fn update_post( ) .await?; + // handle changes to scheduled_publish_time + let scheduled_publish_time = match ( + orig_post.scheduled_publish_time, + data.scheduled_publish_time, + ) { + // schedule time can be changed if post is still scheduled (and not published yet) + (Some(_), Some(_)) => { + Some(convert_published_time(data.scheduled_publish_time, &local_user_view, &context).await?) + } + // post was scheduled, gets changed to publish immediately + (Some(_), None) => Some(None), + // unchanged + (_, _) => None, + }; + let post_form = PostUpdateForm { name: data.name.clone(), url, @@ -115,6 +132,7 @@ pub async fn update_post( nsfw: data.nsfw, language_id: data.language_id, updated: Some(Some(naive_now())), + scheduled_publish_time, ..Default::default() }; @@ -123,13 +141,36 @@ pub async fn update_post( .await .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; - generate_post_link_metadata( - updated_post.clone(), - custom_thumbnail.flatten().map(Into::into), - |post| Some(SendActivityData::UpdatePost(post)), - context.reset_request_count(), - ) - .await?; + // send out federation/webmention if necessary + match ( + orig_post.scheduled_publish_time, + data.scheduled_publish_time, + ) { + // schedule was removed, send create activity and webmention + (Some(_), None) => { + let community = Community::read(&mut context.pool(), orig_post.community_id).await?; + send_webmention(updated_post.clone(), community); + generate_post_link_metadata( + updated_post.clone(), + custom_thumbnail.flatten().map(Into::into), + |post| Some(SendActivityData::CreatePost(post)), + context.reset_request_count(), + ) + .await?; + } + // post was already public, send update + (None, _) => { + generate_post_link_metadata( + updated_post.clone(), + custom_thumbnail.flatten().map(Into::into), + |post| Some(SendActivityData::UpdatePost(post)), + context.reset_request_count(), + ) + .await? + } + // schedule was changed, do nothing + (Some(_), Some(_)) => {} + }; build_post_response( context.deref(), diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 43ccbc0a7..6a89dd577 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -1,7 +1,7 @@ use crate::{ - diesel::OptionalExtension, + diesel::{BoolExpressionMethods, OptionalExtension}, newtypes::{CommunityId, DbUrl, PersonId, PostId}, - schema::{post, post_hide, post_like, post_read, post_saved}, + schema::{community, person, post, post_hide, post_like, post_read, post_saved}, source::post::{ Post, PostHide, @@ -20,6 +20,7 @@ use crate::{ functions::coalesce, get_conn, naive_now, + now, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -30,7 +31,7 @@ use crate::{ use ::url::Url; use chrono::{DateTime, Utc}; use diesel::{ - dsl::insert_into, + dsl::{count, insert_into, not}, result::Error, DecoratableTarget, ExpressionMethods, @@ -173,6 +174,7 @@ impl Post { let object_id: DbUrl = object_id.into(); post::table .filter(post::ap_id.eq(object_id)) + .filter(post::scheduled_publish_time.is_null()) .first(conn) .await .optional() @@ -246,6 +248,28 @@ impl Post { .get_results::(conn) .await } + + pub async fn user_scheduled_post_count( + person_id: PersonId, + pool: &mut DbPool<'_>, + ) -> Result { + let conn = &mut get_conn(pool).await?; + + post::table + .inner_join(person::table) + .inner_join(community::table) + // find all posts which have scheduled_publish_time that is in the past + .filter(post::scheduled_publish_time.is_not_null()) + .filter(coalesce(post::scheduled_publish_time, now()).lt(now())) + // make sure the post and community are still around + .filter(not(post::deleted.or(post::removed))) + .filter(not(community::removed.or(community::deleted))) + // only posts by specified user + .filter(post::creator_id.eq(person_id)) + .select(count(post::id)) + .first::(conn) + .await + } } #[async_trait] @@ -459,6 +483,7 @@ mod tests { featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }; // Post Like diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 289032e00..129b00d8b 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -770,6 +770,7 @@ diesel::table! { featured_local -> Bool, url_content_type -> Nullable, alt_text -> Nullable, + scheduled_publish_time -> Nullable } } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index bf719f54f..3819bd773 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -57,6 +57,8 @@ pub struct Post { pub url_content_type: Option, /// An optional alt_text, usable for image posts. pub alt_text: Option, + /// Time at which the post will be published. None means publish immediately. + pub scheduled_publish_time: Option>, } #[derive(Debug, Clone, derive_new::new)] @@ -104,6 +106,8 @@ pub struct PostInsertForm { pub url_content_type: Option, #[new(default)] pub alt_text: Option, + #[new(default)] + pub scheduled_publish_time: Option>, } #[derive(Debug, Clone, Default)] @@ -130,6 +134,7 @@ pub struct PostUpdateForm { pub featured_local: Option, pub url_content_type: Option>, pub alt_text: Option>, + pub scheduled_publish_time: Option>>, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 494536c87..7f3c853f1 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -619,10 +619,7 @@ mod tests { person: inserted_timmy_person.clone(), counts: Default::default(), }; - let site_form = SiteInsertForm::builder() - .name("test site".to_string()) - .instance_id(inserted_instance.id) - .build(); + let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id); let site = Site::create(pool, &site_form).await?; Ok(Data { inserted_instance, @@ -1093,6 +1090,7 @@ mod tests { featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }, community: Community { id: data.inserted_community.id, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 5dee59538..a8d908c7d 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -318,11 +318,18 @@ fn queries<'a>() -> Queries< // hide posts from deleted communities query = query.filter(community::deleted.eq(false)); - // only show deleted posts to creator + // only creator can see deleted posts and unpublished scheduled posts if let Some(person_id) = options.local_user.person_id() { query = query.filter(post::deleted.eq(false).or(post::creator_id.eq(person_id))); + query = query.filter( + post::scheduled_publish_time + .is_null() + .or(post::creator_id.eq(person_id)), + ); } else { - query = query.filter(post::deleted.eq(false)); + query = query + .filter(post::deleted.eq(false)) + .filter(post::scheduled_publish_time.is_null()); } // only show removed posts to admin when viewing user profile @@ -1771,6 +1778,7 @@ mod tests { featured_community: false, featured_local: false, url_content_type: None, + scheduled_publish_time: None, }, my_vote: None, unread_comments: 0, diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 6d5c40b04..e03ff2e23 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -172,6 +172,8 @@ pub enum LemmyErrorType { Unknown(String), CantDeleteSite, UrlLengthOverflow, + PostScheduleTimeMustBeInFuture, + TooManyScheduledPosts, NotFound, } diff --git a/migrations/2024-09-16-095656_schedule-post/down.sql b/migrations/2024-09-16-095656_schedule-post/down.sql new file mode 100644 index 000000000..bd136ca33 --- /dev/null +++ b/migrations/2024-09-16-095656_schedule-post/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE post + DROP COLUMN scheduled_publish_time; + diff --git a/migrations/2024-09-16-095656_schedule-post/up.sql b/migrations/2024-09-16-095656_schedule-post/up.sql new file mode 100644 index 000000000..7be0e9e22 --- /dev/null +++ b/migrations/2024-09-16-095656_schedule-post/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE post + ADD COLUMN scheduled_publish_time timestamptz; + +CREATE INDEX idx_post_scheduled_publish_time ON post (scheduled_publish_time); + diff --git a/src/lib.rs b/src/lib.rs index 3662f6bc8..804ac7aa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,11 +157,6 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> { rate_limit_cell.clone(), ); - let scheduled_tasks = (!args.disable_scheduled_tasks).then(|| { - // Schedules various cleanup tasks for the DB - tokio::task::spawn(scheduled_tasks::setup(context.clone())) - }); - if let Some(prometheus) = SETTINGS.prometheus.clone() { serve_prometheus(prometheus, context.clone())?; } @@ -187,7 +182,14 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> { })) .expect("set function pointer"); let request_data = federation_config.to_request_data(); - let outgoing_activities_task = tokio::task::spawn(handle_outgoing_activities(request_data)); + let outgoing_activities_task = tokio::task::spawn(handle_outgoing_activities( + request_data.reset_request_count(), + )); + + let scheduled_tasks = (!args.disable_scheduled_tasks).then(|| { + // Schedules various cleanup tasks for the DB + tokio::task::spawn(scheduled_tasks::setup(request_data.reset_request_count())) + }); let server = if !args.disable_http_server { if let Some(startup_server_handle) = startup_server_handle { diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index dc93aecb4..b7532d83c 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -1,20 +1,27 @@ +use activitypub_federation::config::Data; use chrono::{DateTime, TimeZone, Utc}; use clokwerk::{AsyncScheduler, TimeUnits as CTimeUnits}; use diesel::{ - dsl::IntervalDsl, + dsl::{exists, not, IntervalDsl}, sql_query, sql_types::{Integer, Timestamptz}, + BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl, QueryableByName, }; use diesel_async::{AsyncPgConnection, RunQueryDsl}; -use lemmy_api_common::context::LemmyContext; +use lemmy_api_common::{ + context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, +}; +use lemmy_api_crud::post::create::send_webmention; use lemmy_db_schema::{ schema::{ captcha_answer, comment, + community, community_person_ban, instance, person, @@ -23,10 +30,13 @@ use lemmy_db_schema::{ sent_activity, }, source::{ + community::Community, instance::{Instance, InstanceForm}, local_user::LocalUser, + post::{Post, PostUpdateForm}, }, - utils::{get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, + traits::Crud, + utils::{functions::coalesce, get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, }; use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; use lemmy_utils::error::LemmyResult; @@ -35,13 +45,13 @@ 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) -> LemmyResult<()> { +pub async fn setup(context: Data) -> LemmyResult<()> { // Setup the connections let mut scheduler = AsyncScheduler::new(); startup_jobs(&mut context.pool()).await; let context_1 = context.clone(); - // Update active counts every hour + // Update active counts expired bans and unpublished posts every hour scheduler.every(CTimeUnits::hour(1)).run(move || { let context = context_1.clone(); @@ -51,23 +61,15 @@ pub async fn setup(context: LemmyContext) -> LemmyResult<()> { } }); - let context_1 = context.clone(); - // Update hot ranks every 15 minutes + let context_1 = context.reset_request_count(); + // Every 10 minutes update hot ranks, delete expired captchas and publish scheduled posts scheduler.every(CTimeUnits::minutes(10)).run(move || { - let context = context_1.clone(); + let context = context_1.reset_request_count(); async move { update_hot_ranks(&mut context.pool()).await; - } - }); - - let context_1 = context.clone(); - // Delete any captcha answers older than ten minutes, every ten minutes - scheduler.every(CTimeUnits::minutes(10)).run(move || { - let context = context_1.clone(); - - async move { delete_expired_captcha_answers(&mut context.pool()).await; + publish_scheduled_posts(&context).await; } }); @@ -94,7 +96,7 @@ pub async fn setup(context: LemmyContext) -> LemmyResult<()> { delete_old_denied_users(&mut context.pool()).await; update_instance_software(&mut context.pool(), context.client()) .await - .map_err(|e| warn!("Failed to update instance software: {e}")) + .inspect_err(|e| warn!("Failed to update instance software: {e}")) .ok(); } }); @@ -279,7 +281,7 @@ async fn delete_expired_captcha_answers(pool: &mut DbPool<'_>) { .map(|_| { info!("Done."); }) - .map_err(|e| error!("Failed to clear old captcha answers: {e}")) + .inspect_err(|e| error!("Failed to clear old captcha answers: {e}")) .ok(); } Err(e) => { @@ -300,7 +302,7 @@ async fn clear_old_activities(pool: &mut DbPool<'_>) { ) .execute(&mut conn) .await - .map_err(|e| error!("Failed to clear old sent activities: {e}")) + .inspect_err(|e| error!("Failed to clear old sent activities: {e}")) .ok(); diesel::delete( @@ -310,7 +312,7 @@ async fn clear_old_activities(pool: &mut DbPool<'_>) { .execute(&mut conn) .await .map(|_| info!("Done.")) - .map_err(|e| error!("Failed to clear old received activities: {e}")) + .inspect_err(|e| error!("Failed to clear old received activities: {e}")) .ok(); } Err(e) => { @@ -325,7 +327,7 @@ async fn delete_old_denied_users(pool: &mut DbPool<'_>) { .map(|_| { info!("Done."); }) - .map_err(|e| error!("Failed to deleted old denied users: {e}")) + .inspect_err(|e| error!("Failed to deleted old denied users: {e}")) .ok(); } @@ -351,7 +353,7 @@ async fn overwrite_deleted_posts_and_comments(pool: &mut DbPool<'_>) { .map(|_| { info!("Done."); }) - .map_err(|e| error!("Failed to overwrite deleted posts: {e}")) + .inspect_err(|e| error!("Failed to overwrite deleted posts: {e}")) .ok(); info!("Overwriting deleted comments..."); @@ -367,7 +369,7 @@ async fn overwrite_deleted_posts_and_comments(pool: &mut DbPool<'_>) { .map(|_| { info!("Done."); }) - .map_err(|e| error!("Failed to overwrite deleted comments: {e}")) + .inspect_err(|e| error!("Failed to overwrite deleted comments: {e}")) .ok(); } Err(e) => { @@ -399,14 +401,14 @@ async fn active_counts(pool: &mut DbPool<'_>) { sql_query(update_site_stmt) .execute(&mut conn) .await - .map_err(|e| error!("Failed to update site stats: {e}")) + .inspect_err(|e| error!("Failed to update site stats: {e}")) .ok(); let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", i.1, i.0); sql_query(update_community_stmt) .execute(&mut conn) .await - .map_err(|e| error!("Failed to update community stats: {e}")) + .inspect_err(|e| error!("Failed to update community stats: {e}")) .ok(); } @@ -433,7 +435,7 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) { .set(person::banned.eq(false)) .execute(&mut conn) .await - .map_err(|e| error!("Failed to update person.banned when expires: {e}")) + .inspect_err(|e| error!("Failed to update person.banned when expires: {e}")) .ok(); diesel::delete( @@ -441,7 +443,7 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) { ) .execute(&mut conn) .await - .map_err(|e| error!("Failed to remove community_ban expired rows: {e}")) + .inspect_err(|e| error!("Failed to remove community_ban expired rows: {e}")) .ok(); } Err(e) => { @@ -450,6 +452,62 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) { } } +/// Find all unpublished posts with scheduled date in the future, and publish them. +async fn publish_scheduled_posts(context: &Data) { + let pool = &mut context.pool(); + let 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( + community_person_ban::table + .filter(community_person_ban::community_id.eq(community::id)) + .filter(community_person_ban::person_id.eq(person::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(); + + 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(); + + // send out post via federation and webmention + let send_activity = SendActivityData::CreatePost(post.clone()); + ActivityChannel::submit_activity(send_activity, context) + .await + .inspect_err(|e| error!("Failed federate scheduled post: {e}")) + .ok(); + send_webmention(post, community); + } + } + Err(e) => { + error!("Failed to get connection from pool: {e}"); + } + } +} + /// Updates the instance software and version. /// /// Does so using the /.well-known/nodeinfo protocol described here: From a65be776e30b51d0d135b8f81d80d324138fe932 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Sep 2024 08:55:09 -0400 Subject: [PATCH 4/7] Remove redundant local_user.auto_expand setting. (#5041) - Fixes #4643 Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com> --- crates/api/src/local_user/save_settings.rs | 1 - crates/api_common/src/person.rs | 2 +- crates/apub/src/api/user_settings_backup.rs | 1 - crates/db_schema/src/schema.rs | 1 - crates/db_schema/src/source/local_user.rs | 4 ---- crates/db_views/src/registration_application_view.rs | 1 - migrations/2024-09-23-133038_remove_auto_expand/down.sql | 3 +++ migrations/2024-09-23-133038_remove_auto_expand/up.sql | 3 +++ 8 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 migrations/2024-09-23-133038_remove_auto_expand/down.sql create mode 100644 migrations/2024-09-23-133038_remove_auto_expand/up.sql diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 029914545..08820cadd 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -130,7 +130,6 @@ pub async fn save_user_settings( send_notifications_to_email: data.send_notifications_to_email, show_nsfw: data.show_nsfw, blur_nsfw: data.blur_nsfw, - auto_expand: data.auto_expand, show_bot_accounts: data.show_bot_accounts, default_post_sort_type, default_comment_sort_type, diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 40e8df4ce..6f1ddfe43 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -84,8 +84,8 @@ pub struct CaptchaResponse { pub struct SaveUserSettings { /// Show nsfw posts. pub show_nsfw: Option, + /// Blur nsfw posts. pub blur_nsfw: Option, - pub auto_expand: Option, /// Your user's theme. pub theme: Option, /// The default post listing type, usually "local" diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 6764f62cb..8acc67da6 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -127,7 +127,6 @@ pub async fn import_settings( show_read_posts: data.settings.as_ref().map(|s| s.show_read_posts), open_links_in_new_tab: data.settings.as_ref().map(|s| s.open_links_in_new_tab), blur_nsfw: data.settings.as_ref().map(|s| s.blur_nsfw), - auto_expand: data.settings.as_ref().map(|s| s.auto_expand), infinite_scroll_enabled: data.settings.as_ref().map(|s| s.infinite_scroll_enabled), post_listing_mode: data.settings.as_ref().map(|s| s.post_listing_mode), ..Default::default() diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 129b00d8b..ab636c7d7 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -459,7 +459,6 @@ diesel::table! { totp_2fa_secret -> Nullable, open_links_in_new_tab -> Bool, blur_nsfw -> Bool, - auto_expand -> Bool, infinite_scroll_enabled -> Bool, admin -> Bool, post_listing_mode -> PostListingModeEnum, diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 876bfa487..d83fa798c 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -49,7 +49,6 @@ pub struct LocalUser { /// Open links in a new tab. pub open_links_in_new_tab: bool, pub blur_nsfw: bool, - pub auto_expand: bool, /// Whether infinite scroll is enabled. pub infinite_scroll_enabled: bool, /// Whether the person is an admin. @@ -104,8 +103,6 @@ pub struct LocalUserInsertForm { #[new(default)] pub blur_nsfw: Option, #[new(default)] - pub auto_expand: Option, - #[new(default)] pub infinite_scroll_enabled: Option, #[new(default)] pub admin: Option, @@ -143,7 +140,6 @@ pub struct LocalUserUpdateForm { pub totp_2fa_secret: Option>, pub open_links_in_new_tab: Option, pub blur_nsfw: Option, - pub auto_expand: Option, pub infinite_scroll_enabled: Option, pub admin: Option, pub post_listing_mode: Option, diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index ba44e927e..94b7e213d 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -235,7 +235,6 @@ mod tests { person_id: inserted_sara_local_user.person_id, email: inserted_sara_local_user.email, show_nsfw: inserted_sara_local_user.show_nsfw, - auto_expand: inserted_sara_local_user.auto_expand, blur_nsfw: inserted_sara_local_user.blur_nsfw, theme: inserted_sara_local_user.theme, default_post_sort_type: inserted_sara_local_user.default_post_sort_type, diff --git a/migrations/2024-09-23-133038_remove_auto_expand/down.sql b/migrations/2024-09-23-133038_remove_auto_expand/down.sql new file mode 100644 index 000000000..04e0155d0 --- /dev/null +++ b/migrations/2024-09-23-133038_remove_auto_expand/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + ADD COLUMN auto_expand boolean NOT NULL DEFAULT FALSE; + diff --git a/migrations/2024-09-23-133038_remove_auto_expand/up.sql b/migrations/2024-09-23-133038_remove_auto_expand/up.sql new file mode 100644 index 000000000..35d706d1b --- /dev/null +++ b/migrations/2024-09-23-133038_remove_auto_expand/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + DROP COLUMN auto_expand; + From 0fab5bed24b4b654452a5cdc2d84cf178b15c4db Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 24 Sep 2024 13:24:28 -0400 Subject: [PATCH 5/7] Add ability to search for Community by its description (or title only). (#5044) - This changes the post_title_only for Search to title_only, since its also used in the community query now. - Fixes #4785 --- crates/api_common/src/site.rs | 2 +- crates/apub/src/api/search.rs | 5 +++-- crates/db_views/src/post_view.rs | 10 ++++------ crates/db_views_actor/src/community_view.rs | 12 +++++++++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index fc7ececc9..1ffabb75a 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -78,7 +78,7 @@ pub struct Search { pub listing_type: Option, pub page: Option, pub limit: Option, - pub post_title_only: Option, + pub title_only: Option, pub post_url_only: Option, pub saved_only: Option, pub liked_only: Option, diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 642c32efa..d9ae20ede 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -47,7 +47,7 @@ pub async fn search( listing_type, page, limit, - post_title_only, + title_only, post_url_only, saved_only, liked_only, @@ -78,7 +78,7 @@ pub async fn search( search_term: Some(q.clone()), page, limit, - title_only: post_title_only, + title_only, url_only: post_url_only, liked_only, disliked_only, @@ -105,6 +105,7 @@ pub async fn search( sort, listing_type, search_term: Some(q.clone()), + title_only, local_user, is_mod_or_admin: is_admin, page, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index a8d908c7d..1510e9a62 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -394,14 +394,12 @@ fn queries<'a>() -> Queries< query = query.filter(post::url.eq(search_term)); } else { let searcher = fuzzy_search(search_term); + let name_filter = post::name.ilike(searcher.clone()); + let body_filter = post::body.ilike(searcher.clone()); query = if options.title_only.unwrap_or_default() { - query.filter(post::name.ilike(searcher)) + query.filter(name_filter) } else { - query.filter( - post::name - .ilike(searcher.clone()) - .or(post::body.ilike(searcher)), - ) + query.filter(name_filter.or(body_filter)) } .filter(not(post::removed.or(post::deleted))); } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index a1b387bea..89724b04d 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -112,9 +112,14 @@ fn queries<'a>() -> Queries< if let Some(search_term) = options.search_term { let searcher = fuzzy_search(&search_term); - query = query - .filter(community::name.ilike(searcher.clone())) - .or_filter(community::title.ilike(searcher)) + let name_filter = community::name.ilike(searcher.clone()); + let title_filter = community::title.ilike(searcher.clone()); + let description_filter = community::description.ilike(searcher.clone()); + query = if options.title_only.unwrap_or_default() { + query.filter(name_filter.or(title_filter)) + } else { + query.filter(name_filter.or(title_filter.or(description_filter))) + } } // Hide deleted and removed for non-admins or mods @@ -229,6 +234,7 @@ pub struct CommunityQuery<'a> { pub sort: Option, pub local_user: Option<&'a LocalUser>, pub search_term: Option, + pub title_only: Option, pub is_mod_or_admin: bool, pub show_nsfw: bool, pub page: Option, From 61a02482ff7da658a1896aa0dc446aee9dbae086 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 24 Sep 2024 19:25:33 +0200 Subject: [PATCH 6/7] Cleanup remaining use of Result (fixes #4862) (#5047) --- crates/api_crud/src/user/create.rs | 8 ++--- crates/db_schema/src/impls/oauth_account.rs | 34 ++------------------- crates/db_schema/src/impls/person.rs | 13 +++++--- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index ec859cd49..bf17d6f8e 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -107,9 +107,7 @@ pub async fn register( check_slurs(&data.username, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?; - if Person::is_username_taken(&mut context.pool(), &data.username).await? { - return Err(LemmyErrorType::UsernameAlreadyExists)?; - } + Person::check_username_taken(&mut context.pool(), &data.username).await?; if let Some(email) = &data.email { LocalUser::check_is_email_taken(&mut context.pool(), email).await?; @@ -329,9 +327,7 @@ pub async fn authenticate_with_oauth( check_slurs(username, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?; - if Person::is_username_taken(&mut context.pool(), username).await? { - return Err(LemmyErrorType::UsernameAlreadyExists)?; - } + Person::check_username_taken(&mut context.pool(), username).await?; // We have to create a person, a local_user, and an oauth_account person = create_person( diff --git a/crates/db_schema/src/impls/oauth_account.rs b/crates/db_schema/src/impls/oauth_account.rs index 921a21d3d..7210b7a37 100644 --- a/crates/db_schema/src/impls/oauth_account.rs +++ b/crates/db_schema/src/impls/oauth_account.rs @@ -1,32 +1,13 @@ use crate::{ - newtypes::{LocalUserId, OAuthProviderId}, + newtypes::LocalUserId, schema::{oauth_account, oauth_account::dsl::local_user_id}, source::oauth_account::{OAuthAccount, OAuthAccountInsertForm}, utils::{get_conn, DbPool}, }; -use diesel::{ - dsl::{exists, insert_into}, - result::Error, - select, - ExpressionMethods, - QueryDsl, -}; +use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; impl OAuthAccount { - pub async fn read( - pool: &mut DbPool<'_>, - for_oauth_provider_id: OAuthProviderId, - for_local_user_id: LocalUserId, - ) -> Result { - let conn = &mut get_conn(pool).await?; - select(exists( - oauth_account::table.find((for_oauth_provider_id, for_local_user_id)), - )) - .get_result(conn) - .await - } - pub async fn create(pool: &mut DbPool<'_>, form: &OAuthAccountInsertForm) -> Result { let conn = &mut get_conn(pool).await?; insert_into(oauth_account::table) @@ -35,17 +16,6 @@ impl OAuthAccount { .await } - pub async fn delete( - pool: &mut DbPool<'_>, - for_oauth_provider_id: OAuthProviderId, - for_local_user_id: LocalUserId, - ) -> Result { - let conn = &mut get_conn(pool).await?; - diesel::delete(oauth_account::table.find((for_oauth_provider_id, for_local_user_id))) - .execute(conn) - .await - } - pub async fn delete_user_accounts( pool: &mut DbPool<'_>, for_local_user_id: LocalUserId, diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 0a0a2b7a2..776243870 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -21,6 +21,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; +use lemmy_utils::{error::LemmyResult, LemmyErrorType}; #[async_trait] impl Crud for Person { @@ -121,16 +122,18 @@ impl Person { .await } - pub async fn is_username_taken(pool: &mut DbPool<'_>, username: &str) -> Result { + pub async fn check_username_taken(pool: &mut DbPool<'_>, username: &str) -> LemmyResult<()> { use diesel::dsl::{exists, select}; let conn = &mut get_conn(pool).await?; - select(exists( + select(not(exists( person::table .filter(lower(person::name).eq(username.to_lowercase())) .filter(person::local.eq(true)), - )) - .get_result(conn) - .await + ))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::UsernameAlreadyExists.into()) } } From f6a24e133a7a418b1a276faee0895a1e2432c522 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Tue, 24 Sep 2024 19:29:02 +0200 Subject: [PATCH 7/7] Replace clippy allow annotation with expect (fixes #5012) (#5048) --- crates/api/src/lib.rs | 2 -- crates/api/src/site/registration_applications/tests.rs | 9 ++++----- crates/api/src/sitemap.rs | 2 +- crates/api_common/src/claims.rs | 3 +-- crates/api_common/src/request.rs | 3 +-- crates/api_common/src/utils.rs | 3 +-- crates/api_crud/src/site/create.rs | 2 -- crates/api_crud/src/site/mod.rs | 2 -- crates/api_crud/src/site/update.rs | 2 -- crates/apub/src/activity_lists.rs | 1 - crates/apub/src/api/user_settings_backup.rs | 3 ++- crates/apub/src/collections/community_moderators.rs | 2 +- crates/apub/src/http/community.rs | 3 +-- crates/db_perf/src/series.rs | 2 +- crates/db_schema/src/aggregates/comment_aggregates.rs | 3 +-- crates/db_schema/src/aggregates/community_aggregates.rs | 3 +-- crates/db_schema/src/aggregates/person_aggregates.rs | 3 +-- crates/db_schema/src/aggregates/post_aggregates.rs | 4 ++-- crates/db_schema/src/aggregates/site_aggregates.rs | 4 ++-- crates/db_schema/src/impls/activity.rs | 3 +-- crates/db_schema/src/impls/actor_language.rs | 4 ++-- crates/db_schema/src/impls/captcha_answer.rs | 2 -- crates/db_schema/src/impls/comment.rs | 3 +-- crates/db_schema/src/impls/community.rs | 1 - crates/db_schema/src/impls/federation_allowlist.rs | 3 +-- crates/db_schema/src/impls/language.rs | 4 ++-- crates/db_schema/src/impls/local_user.rs | 1 - crates/db_schema/src/impls/moderator.rs | 3 +-- crates/db_schema/src/impls/password_reset_request.rs | 2 -- crates/db_schema/src/impls/person.rs | 1 - crates/db_schema/src/impls/post.rs | 3 +-- crates/db_schema/src/impls/post_report.rs | 3 +-- crates/db_schema/src/impls/private_message.rs | 3 +-- crates/db_schema/src/lib.rs | 1 - crates/db_schema/src/newtypes.rs | 4 ++-- crates/db_schema/src/utils.rs | 1 - crates/db_views/src/comment_report_view.rs | 4 ++-- crates/db_views/src/comment_view.rs | 4 ++-- crates/db_views/src/post_report_view.rs | 4 ++-- crates/db_views/src/post_view.rs | 2 +- crates/db_views/src/private_message_report_view.rs | 4 ++-- crates/db_views/src/private_message_view.rs | 4 ++-- crates/db_views/src/registration_application_view.rs | 3 +-- crates/db_views/src/vote_view.rs | 3 +-- crates/db_views_actor/src/comment_reply_view.rs | 1 - crates/db_views_actor/src/community_view.rs | 3 +-- crates/db_views_actor/src/person_mention_view.rs | 1 - crates/db_views_actor/src/person_view.rs | 2 +- crates/federate/src/inboxes.rs | 4 ++-- crates/federate/src/lib.rs | 4 ++-- crates/federate/src/worker.rs | 4 ++-- crates/utils/src/rate_limit/mod.rs | 2 -- crates/utils/src/rate_limit/rate_limiter.rs | 7 +------ crates/utils/src/utils/markdown/mod.rs | 3 +-- crates/utils/src/utils/markdown/spoiler_rule.rs | 2 -- crates/utils/src/utils/mention.rs | 3 +-- crates/utils/src/utils/slurs.rs | 3 +-- crates/utils/src/utils/validation.rs | 1 - src/scheduled_tasks.rs | 1 - src/session_middleware.rs | 3 +-- 60 files changed, 59 insertions(+), 111 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 44a8d9bbc..6ffa52f77 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -265,8 +265,6 @@ pub async fn local_user_view_from_jwt( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/api/src/site/registration_applications/tests.rs b/crates/api/src/site/registration_applications/tests.rs index 30cbdde72..dd6c0694a 100644 --- a/crates/api/src/site/registration_applications/tests.rs +++ b/crates/api/src/site/registration_applications/tests.rs @@ -34,7 +34,7 @@ use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API}; use serial_test::serial; -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] async fn create_test_site(context: &Data) -> LemmyResult<(Instance, LocalUserView)> { let pool = &mut context.pool(); @@ -109,7 +109,7 @@ async fn signup( Ok((local_user, application)) } -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] async fn get_application_statuses( context: &Data, admin: LocalUserView, @@ -138,10 +138,9 @@ async fn get_application_statuses( Ok((application_count, unread_applications, all_applications)) } -#[allow(clippy::indexing_slicing)] -#[allow(clippy::unwrap_used)] -#[tokio::test] #[serial] +#[tokio::test] +#[expect(clippy::indexing_slicing)] async fn test_application_approval() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; let pool = &mut context.pool(); diff --git a/crates/api/src/sitemap.rs b/crates/api/src/sitemap.rs index bd0e0dad8..2d06a1249 100644 --- a/crates/api/src/sitemap.rs +++ b/crates/api/src/sitemap.rs @@ -42,7 +42,7 @@ pub async fn get_sitemap(context: Data) -> LemmyResult } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::site::create::validate_create_payload; diff --git a/crates/api_crud/src/site/mod.rs b/crates/api_crud/src/site/mod.rs index 462d2f072..48b819c38 100644 --- a/crates/api_crud/src/site/mod.rs +++ b/crates/api_crud/src/site/mod.rs @@ -48,8 +48,6 @@ fn not_zero(val: Option) -> Option { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::site::{application_question_check, not_zero, site_default_post_listing_type_check}; diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index daa0bc49e..8b1934572 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -241,8 +241,6 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::site::update::validate_update_payload; diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index e895ad1cc..9262236d8 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -123,7 +123,6 @@ impl InCommunity for AnnouncableActivities { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 8acc67da6..65ab01d7a 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -307,8 +307,9 @@ where }); Ok(failed_items.into_iter().join(",")) } + #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::api::user_settings_backup::{export_settings, import_settings, UserSettingsBackup}; diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 8e5419c7e..c7b925f97 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -98,7 +98,7 @@ impl Collection for ApubCommunityModerators { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 53d25be62..c61d4fedc 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -120,8 +120,7 @@ pub(crate) async fn get_apub_community_featured( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] pub(crate) mod tests { use super::*; diff --git a/crates/db_perf/src/series.rs b/crates/db_perf/src/series.rs index b504efc54..8efc078b1 100644 --- a/crates/db_perf/src/series.rs +++ b/crates/db_perf/src/series.rs @@ -75,7 +75,7 @@ impl> ValidGrouping<()> type IsAggregate = is_aggregate::No; } -#[allow(non_camel_case_types)] +#[expect(non_camel_case_types)] #[derive(QueryId, Clone, Copy, Debug)] pub struct current_value; diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 150899763..b10c5d9db 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -30,8 +30,7 @@ impl CommentAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index 1e52052f6..699c4cb43 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -36,8 +36,7 @@ impl CommunityAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 337af5ef3..f5ca95c86 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -20,8 +20,7 @@ impl PersonAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index f15567d8f..01f83ec56 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -49,8 +49,8 @@ impl PostAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] + mod tests { use crate::{ diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index 81d6b559d..ebc1fc65d 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -15,8 +15,8 @@ impl SiteAggregates { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] + mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/activity.rs b/crates/db_schema/src/impls/activity.rs index 6b057d254..fef85a3ba 100644 --- a/crates/db_schema/src/impls/activity.rs +++ b/crates/db_schema/src/impls/activity.rs @@ -58,8 +58,7 @@ impl ReceivedActivity { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use super::*; diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index 958c5b1e6..35926de23 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -392,8 +392,8 @@ async fn convert_read_languages( } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/db_schema/src/impls/captcha_answer.rs b/crates/db_schema/src/impls/captcha_answer.rs index 976d89515..d7183e4fb 100644 --- a/crates/db_schema/src/impls/captcha_answer.rs +++ b/crates/db_schema/src/impls/captcha_answer.rs @@ -51,8 +51,6 @@ impl CaptchaAnswer { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 0a4d7dd0e..3a787a5c9 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -196,8 +196,7 @@ impl Saveable for CommentSaved { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index f106ca424..223a1dbf7 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -431,7 +431,6 @@ impl ApubActor for Community { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ source::{ diff --git a/crates/db_schema/src/impls/federation_allowlist.rs b/crates/db_schema/src/impls/federation_allowlist.rs index 80408a526..e430c6fb9 100644 --- a/crates/db_schema/src/impls/federation_allowlist.rs +++ b/crates/db_schema/src/impls/federation_allowlist.rs @@ -48,8 +48,7 @@ impl FederationAllowList { } } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs index 6a7b4e9ac..96992a614 100644 --- a/crates/db_schema/src/impls/language.rs +++ b/crates/db_schema/src/impls/language.rs @@ -41,8 +41,8 @@ impl Language { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{source::language::Language, utils::build_db_pool_for_tests}; diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index a06f22e34..235f053c1 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -369,7 +369,6 @@ pub struct UserBackupLists { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ source::{ diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index 14964aa00..ecbb38ddf 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -465,8 +465,7 @@ impl Crud for AdminPurgeComment { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index 8ce7640a5..015db5581 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -42,8 +42,6 @@ impl PasswordResetRequest { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 776243870..0e6dc8556 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -235,7 +235,6 @@ impl PersonFollower { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 6a89dd577..075f72d23 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -392,8 +392,7 @@ impl PostHide { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/post_report.rs b/crates/db_schema/src/impls/post_report.rs index ccb2f83fb..cad3db2b6 100644 --- a/crates/db_schema/src/impls/post_report.rs +++ b/crates/db_schema/src/impls/post_report.rs @@ -80,8 +80,7 @@ impl Reportable for PostReport { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use super::*; diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index d0ca7378e..9387c0e96 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -85,8 +85,7 @@ impl PrivateMessage { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 7c57ddf28..963ac63d7 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -27,7 +27,6 @@ pub mod newtypes; pub mod sensitive; #[cfg(feature = "full")] #[rustfmt::skip] -#[allow(clippy::wildcard_imports)] pub mod schema; #[cfg(feature = "full")] pub mod aliases { diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index 58396c66a..fe1febef5 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -191,13 +191,13 @@ impl Display for DbUrl { } // the project doesn't compile with From -#[allow(clippy::from_over_into)] +#[expect(clippy::from_over_into)] impl Into for Url { fn into(self) -> DbUrl { DbUrl(Box::new(self)) } } -#[allow(clippy::from_over_into)] +#[expect(clippy::from_over_into)] impl Into for DbUrl { fn into(self) -> Url { *self.0 diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index fb9c542df..1e56563bc 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -595,7 +595,6 @@ impl Queries { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 42b5c3238..4955dabea 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -259,8 +259,8 @@ impl CommentReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 7f3c853f1..626a56b16 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -422,8 +422,8 @@ impl<'a> CommentQuery<'a> { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] -#[allow(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 527ebdafa..fa9941990 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -284,8 +284,8 @@ impl PostReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 1510e9a62..9ab9ad776 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -739,7 +739,7 @@ impl<'a> PostQuery<'a> { } #[cfg(test)] -#[allow(clippy::unwrap_used)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{ post_view::{PaginationCursorData, PostQuery, PostView}, diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/private_message_report_view.rs index 49b8e415b..c4bbf89ac 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -111,8 +111,8 @@ impl PrivateMessageReportQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::private_message_report_view::PrivateMessageReportQuery; diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index cd475897f..cfac287d1 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -173,8 +173,8 @@ impl PrivateMessageQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use crate::{private_message_view::PrivateMessageQuery, structs::PrivateMessageView}; diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 94b7e213d..32760a891 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -135,8 +135,7 @@ impl RegistrationApplicationQuery { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::registration_application_view::{ diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index c97f60e28..c077a778d 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -83,8 +83,7 @@ impl VoteView { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::structs::VoteView; diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 6f8810986..1b657866a 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -303,7 +303,6 @@ impl CommentReplyQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{comment_reply_view::CommentReplyQuery, structs::CommentReplyView}; diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 89724b04d..804d67152 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -248,8 +248,7 @@ impl<'a> CommunityQuery<'a> { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use crate::{community_view::CommunityQuery, structs::CommunityView}; diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 102f95c87..2478c0183 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -303,7 +303,6 @@ impl PersonMentionQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView}; diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index ff4a595ff..724a700ad 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -164,7 +164,7 @@ impl PersonQuery { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; diff --git a/crates/federate/src/inboxes.rs b/crates/federate/src/inboxes.rs index cda4da39b..d57149913 100644 --- a/crates/federate/src/inboxes.rs +++ b/crates/federate/src/inboxes.rs @@ -222,8 +222,8 @@ impl CommunityInboxCollector { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod tests { use super::*; use lemmy_db_schema::{ diff --git a/crates/federate/src/lib.rs b/crates/federate/src/lib.rs index 0e43ef018..983749de3 100644 --- a/crates/federate/src/lib.rs +++ b/crates/federate/src/lib.rs @@ -192,8 +192,8 @@ impl SendManager { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod test { use super::*; diff --git a/crates/federate/src/worker.rs b/crates/federate/src/worker.rs index 56f42bb30..0d4ad04d5 100644 --- a/crates/federate/src/worker.rs +++ b/crates/federate/src/worker.rs @@ -439,8 +439,8 @@ impl InstanceWorker { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] +#[expect(clippy::indexing_slicing)] mod test { use super::*; diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index de64bac46..a6cf92150 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -221,8 +221,6 @@ fn parse_ip(addr: &str) -> Option { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { #[test] diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index a3c6f6a27..e93adfa83 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -136,7 +136,6 @@ impl MapLevel for Map { .entry(addr_part) .or_insert(RateLimitedGroup::new(now, adjusted_configs)); - #[allow(clippy::indexing_slicing)] let total_passes = group.check_total(action_type, now, adjusted_configs[action_type]); let children_pass = group.children.check( @@ -161,7 +160,6 @@ impl MapLevel for Map { // Evaluated if `some_children_remaining` is false let total_has_refill_in_future = || { group.total.into_iter().any(|(action_type, bucket)| { - #[allow(clippy::indexing_slicing)] let config = configs[action_type]; bucket.update(now, config).tokens != config.capacity }) @@ -214,7 +212,6 @@ impl RateLimitedGroup { now: InstantSecs, config: BucketConfig, ) -> bool { - #[allow(clippy::indexing_slicing)] // `EnumMap` has no `get` function let bucket = &mut self.total[action_type]; let new_bucket = bucket.update(now, config); @@ -311,8 +308,7 @@ fn split_ipv6(ip: Ipv6Addr) -> ([u8; 6], u8, u8) { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup}; @@ -361,7 +357,6 @@ mod tests { assert!(post_passed); } - #[allow(clippy::indexing_slicing)] let expected_buckets = |factor: u32, tokens_consumed: u32| { let adjusted_configs = bucket_configs.map(|_, config| BucketConfig { capacity: config.capacity.saturating_mul(factor), diff --git a/crates/utils/src/utils/markdown/mod.rs b/crates/utils/src/utils/markdown/mod.rs index 7ed553e06..79b528aff 100644 --- a/crates/utils/src/utils/markdown/mod.rs +++ b/crates/utils/src/utils/markdown/mod.rs @@ -107,8 +107,7 @@ pub fn markdown_check_for_blocked_urls(text: &str, blocklist: &RegexSet) -> Lemm } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use super::*; diff --git a/crates/utils/src/utils/markdown/spoiler_rule.rs b/crates/utils/src/utils/markdown/spoiler_rule.rs index caced310a..fd6450b31 100644 --- a/crates/utils/src/utils/markdown/spoiler_rule.rs +++ b/crates/utils/src/utils/markdown/spoiler_rule.rs @@ -134,8 +134,6 @@ pub fn add(markdown_parser: &mut MarkdownIt) { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::utils::markdown::spoiler_rule::add; diff --git a/crates/utils/src/utils/mention.rs b/crates/utils/src/utils/mention.rs index c7cc2043f..13762ed27 100644 --- a/crates/utils/src/utils/mention.rs +++ b/crates/utils/src/utils/mention.rs @@ -34,8 +34,7 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::indexing_slicing)] mod test { use crate::utils::mention::scrape_text_for_mentions; diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index ba94372fa..0ce5b825e 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -61,8 +61,7 @@ pub(crate) fn slurs_vec_to_str(slurs: &[&str]) -> String { } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod test { use crate::utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str}; diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 9493ca2fc..98b9a2a25 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -351,7 +351,6 @@ pub fn build_url_str_without_scheme(url_str: &str) -> LemmyResult { } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::{ diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index b7532d83c..2f99fe8a1 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -605,7 +605,6 @@ async fn build_update_instance_form( } #[cfg(test)] -#[allow(clippy::indexing_slicing)] mod tests { use crate::scheduled_tasks::build_update_instance_form; diff --git a/src/session_middleware.rs b/src/session_middleware.rs index b23f8644e..d2eedcb11 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -97,8 +97,7 @@ where } #[cfg(test)] -#[allow(clippy::unwrap_used)] -#[allow(clippy::indexing_slicing)] +#[expect(clippy::unwrap_used)] mod tests { use super::*;