]> Untitled Git - lemmy.git/commitdiff
Showing # of unread comments for posts. Fixes #2134 (#2393)
authorDessalines <dessalines@users.noreply.github.com>
Tue, 27 Sep 2022 16:45:46 +0000 (12:45 -0400)
committerGitHub <noreply@github.com>
Tue, 27 Sep 2022 16:45:46 +0000 (16:45 +0000)
* Showing # of unread comments for posts. Fixes #2134

* Fix lint.

* Forgot to remove comment list update.

* Fix clippy

crates/api_crud/src/comment/list.rs
crates/api_crud/src/post/read.rs
crates/db_schema/src/aggregates/mod.rs
crates/db_schema/src/aggregates/person_post_aggregates.rs [new file with mode: 0644]
crates/db_schema/src/aggregates/structs.rs
crates/db_schema/src/schema.rs
crates/db_views/src/post_view.rs
crates/db_views/src/structs.rs
migrations/2022-08-05-203502_add_person_post_aggregates/down.sql [new file with mode: 0644]
migrations/2022-08-05-203502_add_person_post_aggregates/up.sql [new file with mode: 0644]

index e7632cba2dd1fe441b5836862de9f923c929ca00..e8ff8c1800cf01cca1a8b13a7de39fb601ad7da8 100644 (file)
@@ -63,6 +63,7 @@ impl PerformCrud for GetComments {
       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| {
@@ -74,7 +75,7 @@ impl PerformCrud for GetComments {
         .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)
index fbab8d67191cc810a9cbe1a93f3b6820974d953b..a59e8197b45cb037e27223d833ce65cf5a0d7fec 100644 (file)
@@ -5,6 +5,7 @@ use lemmy_api_common::{
   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},
 };
@@ -64,6 +65,23 @@ impl PerformCrud for GetPost {
     .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 {
index 03ab1b89a0dc991d460a86add4fd14589b61adf1..d55f188f3fc22660765c3b739b7144c14c8dce75 100644 (file)
@@ -5,6 +5,8 @@ pub mod community_aggregates;
 #[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;
diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs
new file mode 100644 (file)
index 0000000..2d268d4
--- /dev/null
@@ -0,0 +1,27 @@
+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)
+  }
+}
index 15fce13b2bb849d00d6167dabfa682702bf653fc..e526b49ddaa6a60155ff1c5dd56272d2b7de9968 100644 (file)
@@ -6,6 +6,7 @@ use crate::schema::{
   comment_aggregates,
   community_aggregates,
   person_aggregates,
+  person_post_aggregates,
   post_aggregates,
   site_aggregates,
 };
@@ -76,6 +77,28 @@ pub struct PostAggregates {
 
 #[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 {
index e0d7167e7bf1b53467bcf772947326902b9aacef..d7f477b7ffafedaf6ac4e6dbd56890cc33a564c5 100644 (file)
@@ -380,6 +380,16 @@ table! {
     }
 }
 
+table! {
+    person_post_aggregates (id) {
+        id -> Int4,
+        person_id -> Int4,
+        post_id -> Int4,
+        read_comments -> Int8,
+        published -> Timestamp,
+    }
+}
+
 table! {
     post_aggregates (id) {
         id -> Int4,
@@ -667,6 +677,8 @@ joinable!(comment_reply -> comment (comment_id));
 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));
@@ -725,6 +737,7 @@ allow_tables_to_appear_in_same_query!(
   person_ban,
   person_block,
   person_mention,
+  person_post_aggregates,
   comment_reply,
   post,
   post_aggregates,
index 778cf98a3d4ddb158ed51e986e11027eb6784eee..bba59ac690b9d945ca4d302609c9a8c9d6d47e35 100644 (file)
@@ -11,6 +11,7 @@ use lemmy_db_schema::{
     local_user_language,
     person,
     person_block,
+    person_post_aggregates,
     post,
     post_aggregates,
     post_like,
@@ -43,8 +44,11 @@ type PostViewTuple = (
   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,
@@ -64,6 +68,7 @@ impl PostView {
       read,
       creator_blocked,
       post_like,
+      unread_comments,
     ) = post::table
       .find(post_id)
       .inner_join(person::table)
@@ -116,6 +121,13 @@ impl PostView {
             .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(),
@@ -127,6 +139,10 @@ impl PostView {
         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)?;
 
@@ -149,6 +165,7 @@ impl PostView {
       read: read.is_some(),
       creator_blocked: creator_blocked.is_some(),
       my_vote,
+      unread_comments,
     })
   }
 }
@@ -237,6 +254,13 @@ impl<'a> PostQuery<'a> {
             .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
@@ -255,6 +279,10 @@ impl<'a> PostQuery<'a> {
         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();
 
@@ -412,6 +440,7 @@ impl ViewToVec for PostView {
         read: a.7.is_some(),
         creator_blocked: a.8.is_some(),
         my_vote: a.9,
+        unread_comments: a.10,
       })
       .collect::<Vec<Self>>()
   }
@@ -806,6 +835,7 @@ mod tests {
         language_id: LanguageId(47),
       },
       my_vote: None,
+      unread_comments: 0,
       creator: PersonSafe {
         id: inserted_person.id,
         name: inserted_person.name.clone(),
index 347f0129273bd9b3d7f3cc90f18b64c8797dbe41..5b068d9aed24efbcb1f20062a0a4b5d611bc4d94 100644 (file)
@@ -85,6 +85,7 @@ pub struct PostView {
   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)]
diff --git a/migrations/2022-08-05-203502_add_person_post_aggregates/down.sql b/migrations/2022-08-05-203502_add_person_post_aggregates/down.sql
new file mode 100644 (file)
index 0000000..f4ae526
--- /dev/null
@@ -0,0 +1 @@
+drop table person_post_aggregates;
diff --git a/migrations/2022-08-05-203502_add_person_post_aggregates/up.sql b/migrations/2022-08-05-203502_add_person_post_aggregates/up.sql
new file mode 100644 (file)
index 0000000..9a0a5fa
--- /dev/null
@@ -0,0 +1,11 @@
+-- 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)
+);