mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-22 20:43:05 +00:00
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
This commit is contained in:
parent
02ba54c589
commit
bd0e68fb00
16 changed files with 67 additions and 54 deletions
|
@ -142,6 +142,7 @@ pub async fn save_user_settings(
|
|||
enable_keyboard_navigation: data.enable_keyboard_navigation,
|
||||
enable_animated_images: data.enable_animated_images,
|
||||
collapse_bot_comments: data.collapse_bot_comments,
|
||||
auto_mark_fetched_posts_as_read: data.auto_mark_fetched_posts_as_read,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
@ -5,19 +5,13 @@ 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::{
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostLike, PostLikeForm},
|
||||
post::{Post, PostLike, PostLikeForm, PostRead},
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
|
@ -73,7 +67,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?;
|
||||
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
|
||||
use lemmy_db_schema::source::post::PostRead;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||
use std::collections::HashSet;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_post_as_read(
|
||||
|
@ -11,7 +10,7 @@ pub async fn mark_post_as_read(
|
|||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
||||
let post_ids = &data.post_ids;
|
||||
|
||||
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||
Err(LemmyErrorType::TooManyItems)?;
|
||||
|
@ -21,13 +20,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_ids, person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||
PostRead::mark_as_read(&mut context.pool(), post_ids, person_id).await?;
|
||||
} else {
|
||||
PostRead::mark_as_unread(&mut context.pool(), post_ids, person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||
PostRead::mark_as_unread(&mut context.pool(), post_ids, person_id).await?;
|
||||
}
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
|
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
@ -138,6 +138,8 @@ pub struct SaveUserSettings {
|
|||
pub show_upvotes: Option<bool>,
|
||||
pub show_downvotes: Option<bool>,
|
||||
pub show_upvote_percentage: Option<bool>,
|
||||
/// Whether to automatically mark fetched posts as read.
|
||||
pub auto_mark_fetched_posts_as_read: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ use lemmy_utils::{
|
|||
use moka::future::Cache;
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
use rosetta_i18n::{Language, LanguageId};
|
||||
use std::{collections::HashSet, sync::LazyLock};
|
||||
use std::sync::LazyLock;
|
||||
use tracing::warn;
|
||||
use url::{ParseError, Url};
|
||||
use urlencoding::encode;
|
||||
|
@ -140,19 +140,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, HashSet::from([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(
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -22,7 +21,7 @@ use lemmy_db_schema::{
|
|||
actor_language::CommunityLanguage,
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm},
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostRead},
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
utils::diesel_url_create,
|
||||
|
@ -169,7 +168,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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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},
|
||||
|
@ -88,6 +91,14 @@ pub async fn list_posts(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
|
||||
|
||||
// If in their user settings, auto-mark fetched posts as read
|
||||
if let Some(local_user) = local_user {
|
||||
if local_user.auto_mark_fetched_posts_as_read {
|
||||
let post_ids = posts.iter().map(|p| p.post.id).collect::<Vec<PostId>>();
|
||||
PostRead::mark_as_read(&mut context.pool(), &post_ids, 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 }))
|
||||
|
|
|
@ -39,6 +39,10 @@ use diesel::{
|
|||
TextExpressionMethods,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyResult},
|
||||
LemmyErrorType,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[async_trait]
|
||||
|
@ -322,36 +326,41 @@ impl Saveable for PostSaved {
|
|||
impl PostRead {
|
||||
pub async fn mark_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
post_ids: HashSet<PostId>,
|
||||
post_ids: &[PostId],
|
||||
person_id: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> LemmyResult<usize> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let forms = post_ids
|
||||
.into_iter()
|
||||
.map(|post_id| PostReadForm { post_id, person_id })
|
||||
.iter()
|
||||
.map(|post_id| PostReadForm {
|
||||
post_id: *post_id,
|
||||
person_id,
|
||||
})
|
||||
.collect::<Vec<PostReadForm>>();
|
||||
insert_into(post_read::table)
|
||||
.values(forms)
|
||||
.on_conflict_do_nothing()
|
||||
.execute(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
||||
}
|
||||
|
||||
pub async fn mark_as_unread(
|
||||
pool: &mut DbPool<'_>,
|
||||
post_id_: HashSet<PostId>,
|
||||
post_ids: &[PostId],
|
||||
person_id_: PersonId,
|
||||
) -> Result<usize, Error> {
|
||||
) -> LemmyResult<usize> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
diesel::delete(
|
||||
post_read::table
|
||||
.filter(post_read::post_id.eq_any(post_id_))
|
||||
.filter(post_read::post_id.eq_any(post_ids))
|
||||
.filter(post_read::person_id.eq(person_id_)),
|
||||
)
|
||||
.execute(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +426,6 @@ mod tests {
|
|||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
use std::collections::HashSet;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -521,7 +529,7 @@ mod tests {
|
|||
// Post Read
|
||||
let marked_as_read = PostRead::mark_as_read(
|
||||
pool,
|
||||
HashSet::from([inserted_post.id, inserted_post2.id]),
|
||||
&[inserted_post.id, inserted_post2.id],
|
||||
inserted_person.id,
|
||||
)
|
||||
.await?;
|
||||
|
@ -545,7 +553,7 @@ mod tests {
|
|||
assert_eq!(1, saved_removed);
|
||||
let read_removed = PostRead::mark_as_unread(
|
||||
pool,
|
||||
HashSet::from([inserted_post.id, inserted_post2.id]),
|
||||
&[inserted_post.id, inserted_post2.id],
|
||||
inserted_person.id,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -474,6 +474,7 @@ diesel::table! {
|
|||
enable_animated_images -> Bool,
|
||||
collapse_bot_comments -> Bool,
|
||||
default_comment_sort_type -> CommentSortTypeEnum,
|
||||
auto_mark_fetched_posts_as_read -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,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)]
|
||||
|
@ -119,6 +121,8 @@ pub struct LocalUserInsertForm {
|
|||
pub collapse_bot_comments: Option<bool>,
|
||||
#[new(default)]
|
||||
pub default_comment_sort_type: Option<CommentSortType>,
|
||||
#[new(default)]
|
||||
pub auto_mark_fetched_posts_as_read: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -149,4 +153,5 @@ pub struct LocalUserUpdateForm {
|
|||
pub enable_animated_images: Option<bool>,
|
||||
pub collapse_bot_comments: Option<bool>,
|
||||
pub default_comment_sort_type: Option<CommentSortType>,
|
||||
pub auto_mark_fetched_posts_as_read: Option<bool>,
|
||||
}
|
||||
|
|
|
@ -1644,7 +1644,7 @@ mod tests {
|
|||
// Mark a post as read
|
||||
PostRead::mark_as_read(
|
||||
pool,
|
||||
HashSet::from([data.inserted_bot_post.id]),
|
||||
&[data.inserted_bot_post.id],
|
||||
data.local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -241,6 +241,7 @@ mod tests {
|
|||
enable_keyboard_navigation: inserted_sara_local_user.enable_keyboard_navigation,
|
||||
enable_animated_images: inserted_sara_local_user.enable_animated_images,
|
||||
collapse_bot_comments: inserted_sara_local_user.collapse_bot_comments,
|
||||
auto_mark_fetched_posts_as_read: false,
|
||||
},
|
||||
creator: Person {
|
||||
id: inserted_sara_person.id,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE local_user
|
||||
DROP COLUMN auto_mark_fetched_posts_as_read;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE local_user
|
||||
ADD COLUMN auto_mark_fetched_posts_as_read boolean DEFAULT FALSE NOT NULL;
|
||||
|
Loading…
Reference in a new issue