None
};
+ let parent_path_cloned = parent_path.to_owned();
let post_id = data.post_id;
let local_user = local_user_view.map(|l| l.local_user);
let mut comments = blocking(context.pool(), move |conn| {
.saved_only(saved_only)
.community_id(community_id)
.community_actor_id(community_actor_id)
- .parent_path(parent_path)
+ .parent_path(parent_path_cloned)
.post_id(post_id)
.local_user(local_user.as_ref())
.page(page)
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read},
};
use lemmy_db_schema::{
+ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
source::comment::Comment,
traits::{Crud, DeleteableOrRemoveable},
};
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
+ // Insert into PersonPostAggregates
+ // to update the read_comments count
+ if let Some(person_id) = person_id {
+ let read_comments = post_view.counts.comments;
+ let person_post_agg_form = PersonPostAggregatesForm {
+ person_id,
+ post_id,
+ read_comments,
+ ..PersonPostAggregatesForm::default()
+ };
+ blocking(context.pool(), move |conn| {
+ PersonPostAggregates::upsert(conn, &person_post_agg_form)
+ })
+ .await?
+ .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?;
+ }
+
// Blank out deleted or removed info for non-logged in users
if person_id.is_none() {
if post_view.post.deleted || post_view.post.removed {
#[cfg(feature = "full")]
pub mod person_aggregates;
#[cfg(feature = "full")]
+pub mod person_post_aggregates;
+#[cfg(feature = "full")]
pub mod post_aggregates;
#[cfg(feature = "full")]
pub mod site_aggregates;
--- /dev/null
+use crate::{
+ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
+ newtypes::{PersonId, PostId},
+};
+use diesel::{result::Error, *};
+
+impl PersonPostAggregates {
+ pub fn upsert(conn: &mut PgConnection, form: &PersonPostAggregatesForm) -> Result<Self, Error> {
+ use crate::schema::person_post_aggregates::dsl::*;
+ insert_into(person_post_aggregates)
+ .values(form)
+ .on_conflict((person_id, post_id))
+ .do_update()
+ .set(form)
+ .get_result::<Self>(conn)
+ }
+ pub fn read(
+ conn: &mut PgConnection,
+ person_id_: PersonId,
+ post_id_: PostId,
+ ) -> Result<Self, Error> {
+ use crate::schema::person_post_aggregates::dsl::*;
+ person_post_aggregates
+ .filter(post_id.eq(post_id_).and(person_id.eq(person_id_)))
+ .first::<Self>(conn)
+ }
+}
comment_aggregates,
community_aggregates,
person_aggregates,
+ person_post_aggregates,
post_aggregates,
site_aggregates,
};
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
+#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
+#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
+pub struct PersonPostAggregates {
+ pub id: i32,
+ pub person_id: PersonId,
+ pub post_id: PostId,
+ pub read_comments: i64,
+ pub published: chrono::NaiveDateTime,
+}
+
+#[derive(Clone, Default)]
+#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
+#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
+pub struct PersonPostAggregatesForm {
+ pub person_id: PersonId,
+ pub post_id: PostId,
+ pub read_comments: i64,
+ pub published: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
#[cfg_attr(feature = "full", diesel(table_name = site_aggregates))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::site::Site)))]
pub struct SiteAggregates {
}
}
+table! {
+ person_post_aggregates (id) {
+ id -> Int4,
+ person_id -> Int4,
+ post_id -> Int4,
+ read_comments -> Int8,
+ published -> Timestamp,
+ }
+}
+
table! {
post_aggregates (id) {
id -> Int4,
joinable!(comment_reply -> person (recipient_id));
joinable!(post -> community (community_id));
joinable!(post -> person (creator_id));
+joinable!(person_post_aggregates -> post (post_id));
+joinable!(person_post_aggregates -> person (person_id));
joinable!(post_aggregates -> post (post_id));
joinable!(post_like -> person (person_id));
joinable!(post_like -> post (post_id));
person_ban,
person_block,
person_mention,
+ person_post_aggregates,
comment_reply,
post,
post_aggregates,
local_user_language,
person,
person_block,
+ person_post_aggregates,
post,
post_aggregates,
post_like,
Option<PostRead>,
Option<PersonBlock>,
Option<i16>,
+ i64,
);
+sql_function!(fn coalesce(x: sql_types::Nullable<sql_types::BigInt>, y: sql_types::BigInt) -> sql_types::BigInt);
+
impl PostView {
pub fn read(
conn: &mut PgConnection,
read,
creator_blocked,
post_like,
+ unread_comments,
) = post::table
.find(post_id)
.inner_join(person::table)
.and(post_like::person_id.eq(person_id_join)),
),
)
+ .left_join(
+ person_post_aggregates::table.on(
+ post::id
+ .eq(person_post_aggregates::post_id)
+ .and(person_post_aggregates::person_id.eq(person_id_join)),
+ ),
+ )
.select((
post::all_columns,
Person::safe_columns_tuple(),
post_read::all_columns.nullable(),
person_block::all_columns.nullable(),
post_like::score.nullable(),
+ coalesce(
+ post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
+ post_aggregates::comments,
+ ),
))
.first::<PostViewTuple>(conn)?;
read: read.is_some(),
creator_blocked: creator_blocked.is_some(),
my_vote,
+ unread_comments,
})
}
}
.and(post_like::person_id.eq(person_id_join)),
),
)
+ .left_join(
+ person_post_aggregates::table.on(
+ post::id
+ .eq(person_post_aggregates::post_id)
+ .and(person_post_aggregates::person_id.eq(person_id_join)),
+ ),
+ )
.left_join(
local_user_language::table.on(
post::language_id
post_read::all_columns.nullable(),
person_block::all_columns.nullable(),
post_like::score.nullable(),
+ coalesce(
+ post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
+ post_aggregates::comments,
+ ),
))
.into_boxed();
read: a.7.is_some(),
creator_blocked: a.8.is_some(),
my_vote: a.9,
+ unread_comments: a.10,
})
.collect::<Vec<Self>>()
}
language_id: LanguageId(47),
},
my_vote: None,
+ unread_comments: 0,
creator: PersonSafe {
id: inserted_person.id,
name: inserted_person.name.clone(),
pub read: bool, // Left join to PostRead
pub creator_blocked: bool, // Left join to PersonBlock
pub my_vote: Option<i16>, // Left join to PostLike
+ pub unread_comments: i64, // Left join to PersonPostAggregates
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
--- /dev/null
+drop table person_post_aggregates;
--- /dev/null
+-- This table stores the # of read comments for a person, on a post
+-- It can then be joined to post_aggregates to get an unread count:
+-- unread = post_aggregates.comments - person_post_aggregates.read_comments
+create table person_post_aggregates(
+ id serial primary key,
+ person_id int references person on update cascade on delete cascade not null,
+ post_id int references post on update cascade on delete cascade not null,
+ read_comments bigint not null default 0,
+ published timestamp not null default now(),
+ unique(person_id, post_id)
+);