From a9d6d4e6e0f13e8885560135355b0c722a5daf32 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 13 Nov 2024 10:05:16 -0500 Subject: [PATCH] Add user setting to auto-mark fetched posts as read. (#5160) * Add user setting to auto-mark fetched posts as read. - Rather than apps collecting up viewed posts ids, and sending many mark as read requests, users can now turn this setting on, and any results from /post/list will be auto-marked as read. - Fixes #5144 * Adding list_post request option to auto-mark as read. * Moving db_perf to before federation tests. * Fixing lemmyerrortype import. * Fixing ts_option. * Fix clippy. * Fix override logic. * Revert "Fix override logic." This reverts commit 923d7f0ecaa3ccc85a62e407082c2f7ea31473fa. * Changing name to mark_as_read --- .woodpecker.yml | 22 +++++++++---------- crates/api/src/local_user/save_settings.rs | 1 + crates/api/src/post/like.rs | 13 ++++------- crates/api/src/post/mark_read.rs | 10 +++------ crates/api/src/post/save.rs | 5 ++--- crates/api_common/src/person.rs | 3 +++ crates/api_common/src/post.rs | 3 +++ crates/api_common/src/utils.rs | 15 +------------ crates/api_crud/src/post/create.rs | 5 ++--- crates/api_crud/src/post/read.rs | 9 +++++--- crates/apub/src/api/list_posts.rs | 19 +++++++++++++++- crates/db_schema/src/impls/post.rs | 7 ++++-- crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/source/local_user.rs | 5 +++++ .../src/registration_application_view.rs | 1 + .../down.sql | 3 +++ .../up.sql | 3 +++ 17 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/down.sql create mode 100644 migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/up.sql diff --git a/.woodpecker.yml b/.woodpecker.yml index 060cc3e26..1a96c2c66 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -138,17 +138,6 @@ steps: - diff tmp.schema crates/db_schema/src/schema.rs when: *slow_check_paths - check_db_perf_tool: - image: *rust_image - environment: - LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy - RUST_BACKTRACE: "1" - CARGO_HOME: .cargo_home - commands: - # same as scripts/db_perf.sh but without creating a new database server - - cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1 - when: *slow_check_paths - cargo_clippy: image: *rust_image environment: @@ -221,6 +210,17 @@ steps: - diff before.sqldump after.sqldump when: *slow_check_paths + check_db_perf_tool: + image: *rust_image + environment: + LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy + RUST_BACKTRACE: "1" + CARGO_HOME: .cargo_home + commands: + # same as scripts/db_perf.sh but without creating a new database server + - cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1 + when: *slow_check_paths + run_federation_tests: image: node:22-bookworm-slim environment: diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index ac2e321a1..992fea163 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -143,6 +143,7 @@ pub async fn save_user_settings( enable_animated_images: data.enable_animated_images, enable_private_messages: data.enable_private_messages, collapse_bot_comments: data.collapse_bot_comments, + auto_mark_fetched_posts_as_read: data.auto_mark_fetched_posts_as_read, ..Default::default() }; diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index ec01e3e8c..e70213765 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -5,18 +5,12 @@ use lemmy_api_common::{ context::LemmyContext, post::{CreatePostLike, PostResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::{ - check_bot_account, - check_community_user_action, - check_local_vote_mode, - mark_post_as_read, - VoteItem, - }, + utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem}, }; use lemmy_db_schema::{ source::{ local_site::LocalSite, - post::{PostLike, PostLikeForm}, + post::{PostLike, PostLikeForm, PostRead}, }, traits::Likeable, }; @@ -72,7 +66,8 @@ pub async fn like_post( .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; } - mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + // Mark Post Read + PostRead::mark_as_read(&mut context.pool(), post_id, person_id).await?; ActivityChannel::submit_activity( SendActivityData::LikePostOrComment { diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index 893be56b6..def7656b1 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::source::post::PostRead; use lemmy_db_views::structs::{LocalUserView, PostView}; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] pub async fn mark_post_as_read( @@ -18,13 +18,9 @@ pub async fn mark_post_as_read( // Mark the post as read / unread if data.read { - PostRead::mark_as_read(&mut context.pool(), post_id, person_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; + PostRead::mark_as_read(&mut context.pool(), post_id, person_id).await?; } else { - PostRead::mark_as_unread(&mut context.pool(), post_id, person_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; + PostRead::mark_as_unread(&mut context.pool(), post_id, person_id).await?; } let post_view = PostView::read( &mut context.pool(), diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index 4549b62b1..4a382e732 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -2,10 +2,9 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, post::{PostResponse, SavePost}, - utils::mark_post_as_read, }; use lemmy_db_schema::{ - source::post::{PostSaved, PostSavedForm}, + source::post::{PostRead, PostSaved, PostSavedForm}, traits::Saveable, }; use lemmy_db_views::structs::{LocalUserView, PostView}; @@ -42,7 +41,7 @@ pub async fn save_post( ) .await?; - mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + PostRead::mark_as_read(&mut context.pool(), post_id, person_id).await?; Ok(Json(PostResponse { post_view })) } diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 742dc88db..b95cf5e77 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -178,6 +178,9 @@ pub struct SaveUserSettings { pub show_downvotes: Option, #[cfg_attr(feature = "full", ts(optional))] pub show_upvote_percentage: Option, + /// Whether to automatically mark fetched posts as read. + #[cfg_attr(feature = "full", ts(optional))] + pub auto_mark_fetched_posts_as_read: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index d5894abd0..39e87e683 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -108,6 +108,9 @@ pub struct GetPosts { /// If true, then show the nsfw posts (even if your user setting is to hide them) #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, + /// Whether to automatically mark fetched posts as read. + #[cfg_attr(feature = "full", ts(optional))] + pub mark_as_read: Option, #[cfg_attr(feature = "full", ts(optional))] /// If true, then only show posts with no comments pub no_comments_only: Option, diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index a462b7147..09cdac28c 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -28,7 +28,7 @@ use lemmy_db_schema::{ password_reset_request::PasswordResetRequest, person::{Person, PersonUpdateForm}, person_block::PersonBlock, - post::{Post, PostLike, PostRead}, + post::{Post, PostLike}, registration_application::RegistrationApplication, site::Site, }, @@ -141,19 +141,6 @@ pub fn is_top_mod( } } -/// Marks a post as read for a given person. -#[tracing::instrument(skip_all)] -pub async fn mark_post_as_read( - person_id: PersonId, - post_id: PostId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - PostRead::mark_as_read(pool, post_id, person_id) - .await - .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; - Ok(()) -} - /// Updates the read comment count for a post. Usually done when reading or creating a new comment. #[tracing::instrument(skip_all)] pub async fn update_read_comments( diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 5dee5e21c..6dc847086 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -12,7 +12,6 @@ use lemmy_api_common::{ get_url_blocklist, honeypot_check, local_site_to_slur_regex, - mark_post_as_read, process_markdown_opt, }, }; @@ -21,7 +20,7 @@ use lemmy_db_schema::{ source::{ community::Community, local_site::LocalSite, - post::{Post, PostInsertForm, PostLike, PostLikeForm}, + post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead}, }, traits::{Crud, Likeable}, utils::diesel_url_create, @@ -153,7 +152,7 @@ pub async fn create_post( .await .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; - mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + PostRead::mark_as_read(&mut context.pool(), post_id, person_id).await?; build_post_response(&context, community_id, local_user_view, post_id).await } diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index 7677d59ef..f3c2e1fa6 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -2,10 +2,13 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPost, GetPostResponse}, - utils::{check_private_instance, is_mod_or_admin_opt, mark_post_as_read, update_read_comments}, + utils::{check_private_instance, is_mod_or_admin_opt, update_read_comments}, }; use lemmy_db_schema::{ - source::{comment::Comment, post::Post}, + source::{ + comment::Comment, + post::{Post, PostRead}, + }, traits::Crud, }; use lemmy_db_views::{ @@ -62,7 +65,7 @@ pub async fn get_post( let post_id = post_view.post.id; if let Some(person_id) = person_id { - mark_post_as_read(person_id, post_id, &mut context.pool()).await?; + PostRead::mark_as_read(&mut context.pool(), post_id, person_id).await?; update_read_comments( person_id, diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index cdf24dbaa..dd80dcbcb 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -10,7 +10,10 @@ use lemmy_api_common::{ post::{GetPosts, GetPostsResponse}, utils::{check_conflicting_like_filters, check_private_instance}, }; -use lemmy_db_schema::source::community::Community; +use lemmy_db_schema::{ + newtypes::PostId, + source::{community::Community, post::PostRead}, +}; use lemmy_db_views::{ post_view::PostQuery, structs::{LocalUserView, PaginationCursor, SiteView}, @@ -90,6 +93,20 @@ pub async fn list_posts( .await .with_lemmy_type(LemmyErrorType::CouldntGetPosts)?; + // If in their user settings (or as part of the API request), auto-mark fetched posts as read + if let Some(local_user) = local_user { + if data + .mark_as_read + .unwrap_or(local_user.auto_mark_fetched_posts_as_read) + { + let post_ids = posts.iter().map(|p| p.post.id).collect::>(); + // TODO get rid of this in the next pr + for post_id in post_ids { + PostRead::mark_as_read(&mut context.pool(), post_id, local_user.person_id).await?; + } + } + } + // if this page wasn't empty, then there is a next page after the last post on this page let next_page = posts.last().map(PaginationCursor::after_post); Ok(Json(GetPostsResponse { posts, next_page })) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index bf969a50e..c8b34d3bc 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -42,6 +42,7 @@ use diesel::{ TextExpressionMethods, }; use diesel_async::RunQueryDsl; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[async_trait] impl Crud for Post { @@ -336,7 +337,7 @@ impl PostRead { pool: &mut DbPool<'_>, post_id: PostId, person_id: PersonId, - ) -> Result { + ) -> LemmyResult { let conn = &mut get_conn(pool).await?; let form = ( @@ -351,13 +352,14 @@ impl PostRead { .set(form) .execute(conn) .await + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) } pub async fn mark_as_unread( pool: &mut DbPool<'_>, post_id_: PostId, person_id_: PersonId, - ) -> Result { + ) -> LemmyResult { let conn = &mut get_conn(pool).await?; uplete::new( @@ -368,6 +370,7 @@ impl PostRead { .set_null(post_actions::read) .get_result(conn) .await + .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead) } } diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 1d6f3f1d0..9e80c4693 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -455,6 +455,7 @@ diesel::table! { enable_private_messages -> Bool, collapse_bot_comments -> Bool, default_comment_sort_type -> CommentSortTypeEnum, + auto_mark_fetched_posts_as_read -> Bool, } } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index fd15253cc..82d16f7d4 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -68,6 +68,8 @@ pub struct LocalUser { /// Whether to auto-collapse bot comments. pub collapse_bot_comments: bool, pub default_comment_sort_type: CommentSortType, + /// Whether to automatically mark fetched posts as read. + pub auto_mark_fetched_posts_as_read: bool, } #[derive(Clone, derive_new::new)] @@ -124,6 +126,8 @@ pub struct LocalUserInsertForm { pub collapse_bot_comments: Option, #[new(default)] pub default_comment_sort_type: Option, + #[new(default)] + pub auto_mark_fetched_posts_as_read: Option, } #[derive(Clone, Default)] @@ -155,4 +159,5 @@ pub struct LocalUserUpdateForm { pub enable_private_messages: Option, pub collapse_bot_comments: Option, pub default_comment_sort_type: Option, + pub auto_mark_fetched_posts_as_read: Option, } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index b5821ef26..0fa0a5d7e 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -242,6 +242,7 @@ mod tests { enable_animated_images: inserted_sara_local_user.enable_animated_images, enable_private_messages: inserted_sara_local_user.enable_private_messages, collapse_bot_comments: inserted_sara_local_user.collapse_bot_comments, + auto_mark_fetched_posts_as_read: false, }, creator: Person { id: inserted_sara_person.id, diff --git a/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/down.sql b/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/down.sql new file mode 100644 index 000000000..0f2ff0569 --- /dev/null +++ b/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + DROP COLUMN auto_mark_fetched_posts_as_read; + diff --git a/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/up.sql b/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/up.sql new file mode 100644 index 000000000..fbe57276b --- /dev/null +++ b/migrations/2024-11-01-233231_add_mark_fetched_posts_as_read/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + ADD COLUMN auto_mark_fetched_posts_as_read boolean DEFAULT FALSE NOT NULL; +