Beginning to add new comment_view.
authorDessalines <tyhou13@gmx.com>
Tue, 15 Dec 2020 15:28:25 +0000 (10:28 -0500)
committerDessalines <tyhou13@gmx.com>
Tue, 15 Dec 2020 15:28:25 +0000 (10:28 -0500)
lemmy_db/src/aggregates/comment_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/mod.rs
lemmy_db/src/schema.rs
lemmy_db/src/source/comment.rs
lemmy_db/src/source/user.rs
lemmy_db/src/views/comment_view.rs [new file with mode: 0644]
lemmy_db/src/views/mod.rs
lemmy_db/src/views/post_view.rs
migrations/2020-12-14-020038_create_comment_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-14-020038_create_comment_aggregates/up.sql [new file with mode: 0644]

diff --git a/lemmy_db/src/aggregates/comment_aggregates.rs b/lemmy_db/src/aggregates/comment_aggregates.rs
new file mode 100644 (file)
index 0000000..7ce52ed
--- /dev/null
@@ -0,0 +1,23 @@
+use crate::schema::comment_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "comment_aggregates"]
+pub struct CommentAggregates {
+  pub id: i32,
+  pub comment_id: i32,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+}
+
+impl CommentAggregates {
+  pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+    comment_aggregates::table
+      .filter(comment_aggregates::comment_id.eq(comment_id))
+      .first::<Self>(conn)
+  }
+}
+
+// TODO add tests here
index e033aede2b876e12e944769d9f0db11d3e4e0670..bdef6591dab58048ed9091b6982a5ef9246f6371 100644 (file)
@@ -1,3 +1,4 @@
+pub mod comment_aggregates;
 pub mod community_aggregates;
 pub mod post_aggregates;
 pub mod site_aggregates;
index b0c57f5e15e23bb919f526b0a8befbb79ba58eb5..5fa5e371cb0ddca080388e76c5bf62e390aa8064 100644 (file)
@@ -34,6 +34,16 @@ table! {
     }
 }
 
+table! {
+    comment_aggregates (id) {
+        id -> Int4,
+        comment_id -> Int4,
+        score -> Int8,
+        upvotes -> Int8,
+        downvotes -> Int8,
+    }
+}
+
 table! {
     comment_aggregates_fast (id) {
         id -> Int4,
@@ -556,8 +566,61 @@ table! {
     }
 }
 
+// These are necessary since diesel doesn't have self joins / aliases
+table! {
+    comment_alias_1 (id) {
+        id -> Int4,
+        creator_id -> Int4,
+        post_id -> Int4,
+        parent_id -> Nullable<Int4>,
+        content -> Text,
+        removed -> Bool,
+        read -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        deleted -> Bool,
+        ap_id -> Varchar,
+        local -> Bool,
+    }
+}
+
+table! {
+    user_alias_1 (id) {
+        id -> Int4,
+        name -> Varchar,
+        preferred_username -> Nullable<Varchar>,
+        password_encrypted -> Text,
+        email -> Nullable<Text>,
+        avatar -> Nullable<Text>,
+        admin -> Bool,
+        banned -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        show_nsfw -> Bool,
+        theme -> Varchar,
+        default_sort_type -> Int2,
+        default_listing_type -> Int2,
+        lang -> Varchar,
+        show_avatars -> Bool,
+        send_notifications_to_email -> Bool,
+        matrix_user_id -> Nullable<Text>,
+        actor_id -> Varchar,
+        bio -> Nullable<Text>,
+        local -> Bool,
+        private_key -> Nullable<Text>,
+        public_key -> Nullable<Text>,
+        last_refreshed_at -> Timestamp,
+        banner -> Nullable<Text>,
+        deleted -> Bool,
+    }
+}
+
+joinable!(comment_alias_1 -> user_alias_1 (creator_id));
+joinable!(comment -> comment_alias_1 (parent_id));
+
 joinable!(comment -> post (post_id));
 joinable!(comment -> user_ (creator_id));
+joinable!(comment_aggregates -> comment (comment_id));
 joinable!(comment_like -> comment (comment_id));
 joinable!(comment_like -> post (post_id));
 joinable!(comment_like -> user_ (user_id));
@@ -606,6 +669,7 @@ allow_tables_to_appear_in_same_query!(
   activity,
   category,
   comment,
+  comment_aggregates,
   comment_aggregates_fast,
   comment_like,
   comment_report,
@@ -641,4 +705,6 @@ allow_tables_to_appear_in_same_query!(
   user_ban,
   user_fast,
   user_mention,
+  comment_alias_1,
+  user_alias_1,
 );
index dd4fb39dee27f9b5fce1adeaa789fe1dca8e1334..239762599a010e4156c773660aab68ffb4dc860e 100644 (file)
@@ -1,13 +1,14 @@
 use super::post::Post;
 use crate::{
   naive_now,
-  schema::{comment, comment_like, comment_saved},
+  schema::{comment, comment_alias_1, comment_like, comment_saved},
   ApubObject,
   Crud,
   Likeable,
   Saveable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 use url::{ParseError, Url};
 
 // WITH RECURSIVE MyTree AS (
@@ -17,7 +18,7 @@ use url::{ParseError, Url};
 // )
 // SELECT * FROM MyTree;
 
-#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[belongs_to(Post)]
 #[table_name = "comment"]
 pub struct Comment {
@@ -35,6 +36,24 @@ pub struct Comment {
   pub local: bool,
 }
 
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[belongs_to(Post)]
+#[table_name = "comment_alias_1"]
+pub struct CommentAlias1 {
+  pub id: i32,
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub parent_id: Option<i32>,
+  pub content: String,
+  pub removed: bool,
+  pub read: bool, // Whether the recipient has read the comment or not
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub deleted: bool,
+  pub ap_id: String,
+  pub local: bool,
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "comment"]
 pub struct CommentForm {
index 5fdb56bbc6788528856aaed5ea280002f0ce5be4..0bd68a50924c57fa2b4e38465ebd75debabf1810 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
   is_email_regex,
   naive_now,
-  schema::{user_, user_::dsl::*},
+  schema::{user_, user_::dsl::*, user_alias_1},
   ApubObject,
   Crud,
 };
@@ -103,6 +103,98 @@ mod safe_type {
   }
 }
 
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub password_encrypted: String,
+  pub email: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub show_nsfw: bool,
+  pub theme: String,
+  pub default_sort_type: i16,
+  pub default_listing_type: i16,
+  pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub private_key: Option<String>,
+  pub public_key: Option<String>,
+  pub last_refreshed_at: chrono::NaiveDateTime,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserSafeAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+mod safe_type_alias {
+  use crate::{schema::user_alias_1::columns::*, source::user::UserAlias1, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for UserAlias1 {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        preferred_username,
+        avatar,
+        admin,
+        banned,
+        published,
+        updated,
+        matrix_user_id,
+        actor_id,
+        bio,
+        local,
+        banner,
+        deleted,
+      )
+    }
+  }
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "user_"]
 pub struct UserForm {
diff --git a/lemmy_db/src/views/comment_view.rs b/lemmy_db/src/views/comment_view.rs
new file mode 100644 (file)
index 0000000..3e81269
--- /dev/null
@@ -0,0 +1,662 @@
+use crate::{
+  aggregates::comment_aggregates::CommentAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{
+    comment,
+    comment_aggregates,
+    comment_alias_1,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+  },
+  source::{
+    comment::{Comment, CommentAlias1, CommentSaved},
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ListingType,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct CommentView {
+  pub comment: Comment,
+  pub creator: UserSafe,
+  pub recipient: Option<UserSafeAlias1>, // Left joins to comment and user
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub saved: bool,                         // Left join to CommentSaved
+  pub my_vote: Option<i16>,                // Left join to CommentLike
+}
+
+type CommentViewTuple = (
+  Comment,
+  UserSafe,
+  Option<CommentAlias1>,
+  Option<UserSafeAlias1>,
+  Post,
+  CommunitySafe,
+  CommentAggregates,
+  Option<CommunityUserBan>,
+  Option<CommunityFollower>,
+  Option<CommentSaved>,
+  Option<i16>,
+);
+
+impl CommentView {
+  pub fn read(
+    conn: &PgConnection,
+    comment_id: i32,
+    my_user_id: Option<i32>,
+  ) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (
+      comment,
+      creator,
+      _parent_comment,
+      recipient,
+      post,
+      community,
+      counts,
+      creator_banned_from_community,
+      subscribed,
+      saved,
+      my_vote,
+    ) = comment::table
+      .find(comment_id)
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .first::<CommentViewTuple>(conn)?;
+
+    Ok(CommentView {
+      comment,
+      recipient,
+      post,
+      creator,
+      community,
+      counts,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      subscribed: subscribed.is_some(),
+      saved: saved.is_some(),
+      my_vote,
+    })
+  }
+}
+
+mod join_types {
+  use crate::schema::{
+    comment,
+    comment_aggregates,
+    comment_alias_1,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+  };
+  use diesel::{
+    pg::Pg,
+    query_builder::BoxedSelectStatement,
+    query_source::joins::{Inner, Join, JoinOn, LeftOuter},
+    sql_types::*,
+  };
+
+  // /// TODO awful, but necessary because of the boxed join
+  pub(super) type BoxedCommentJoin<'a> = BoxedSelectStatement<
+    'a,
+    (
+      (
+        Integer,
+        Integer,
+        Integer,
+        Nullable<Integer>,
+        Text,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Text,
+        Bool,
+      ),
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Nullable<Text>,
+        Text,
+        Nullable<Text>,
+        Bool,
+        Nullable<Text>,
+        Bool,
+      ),
+      Nullable<(
+        Integer,
+        Integer,
+        Integer,
+        Nullable<Integer>,
+        Text,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Text,
+        Bool,
+      )>,
+      Nullable<(
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Nullable<Text>,
+        Text,
+        Nullable<Text>,
+        Bool,
+        Nullable<Text>,
+        Bool,
+      )>,
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Integer,
+        Integer,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Bool,
+        Bool,
+        Nullable<Text>,
+        Nullable<Text>,
+        Nullable<Text>,
+        Nullable<Text>,
+        Text,
+        Bool,
+      ),
+      (
+        Integer,
+        Text,
+        Text,
+        Nullable<Text>,
+        Integer,
+        Integer,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Bool,
+        Text,
+        Bool,
+        Nullable<Text>,
+        Nullable<Text>,
+      ),
+      (Integer, Integer, BigInt, BigInt, BigInt),
+      Nullable<(Integer, Integer, Integer, Timestamp)>,
+      Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
+      Nullable<(Integer, Integer, Integer, Timestamp)>,
+      Nullable<SmallInt>,
+    ),
+    JoinOn<
+      Join<
+        JoinOn<
+          Join<
+            JoinOn<
+              Join<
+                JoinOn<
+                  Join<
+                    JoinOn<
+                      Join<
+                        JoinOn<
+                          Join<
+                            JoinOn<
+                              Join<
+                                JoinOn<
+                                  Join<
+                                    JoinOn<
+                                      Join<
+                                        JoinOn<
+                                          Join<comment::table, user_::table, Inner>,
+                                          diesel::expression::operators::Eq<
+                                            diesel::expression::nullable::Nullable<
+                                              comment::columns::creator_id,
+                                            >,
+                                            diesel::expression::nullable::Nullable<
+                                              user_::columns::id,
+                                            >,
+                                          >,
+                                        >,
+                                        comment_alias_1::table,
+                                        LeftOuter,
+                                      >,
+                                      diesel::expression::operators::Eq<
+                                        diesel::expression::nullable::Nullable<
+                                          comment_alias_1::columns::id,
+                                        >,
+                                        comment::columns::parent_id,
+                                      >,
+                                    >,
+                                    user_alias_1::table,
+                                    LeftOuter,
+                                  >,
+                                  diesel::expression::operators::Eq<
+                                    user_alias_1::columns::id,
+                                    comment_alias_1::columns::creator_id,
+                                  >,
+                                >,
+                                post::table,
+                                Inner,
+                              >,
+                              diesel::expression::operators::Eq<
+                                diesel::expression::nullable::Nullable<comment::columns::post_id>,
+                                diesel::expression::nullable::Nullable<post::columns::id>,
+                              >,
+                            >,
+                            community::table,
+                            Inner,
+                          >,
+                          diesel::expression::operators::Eq<
+                            post::columns::community_id,
+                            community::columns::id,
+                          >,
+                        >,
+                        comment_aggregates::table,
+                        Inner,
+                      >,
+                      diesel::expression::operators::Eq<
+                        diesel::expression::nullable::Nullable<
+                          comment_aggregates::columns::comment_id,
+                        >,
+                        diesel::expression::nullable::Nullable<comment::columns::id>,
+                      >,
+                    >,
+                    community_user_ban::table,
+                    LeftOuter,
+                  >,
+                  diesel::expression::operators::And<
+                    diesel::expression::operators::Eq<
+                      community::columns::id,
+                      community_user_ban::columns::community_id,
+                    >,
+                    diesel::expression::operators::Eq<
+                      community_user_ban::columns::user_id,
+                      comment::columns::creator_id,
+                    >,
+                  >,
+                >,
+                community_follower::table,
+                LeftOuter,
+              >,
+              diesel::expression::operators::And<
+                diesel::expression::operators::Eq<
+                  post::columns::community_id,
+                  community_follower::columns::community_id,
+                >,
+                diesel::expression::operators::Eq<
+                  community_follower::columns::user_id,
+                  diesel::expression::bound::Bound<Integer, i32>,
+                >,
+              >,
+            >,
+            comment_saved::table,
+            LeftOuter,
+          >,
+          diesel::expression::operators::And<
+            diesel::expression::operators::Eq<
+              comment::columns::id,
+              comment_saved::columns::comment_id,
+            >,
+            diesel::expression::operators::Eq<
+              comment_saved::columns::user_id,
+              diesel::expression::bound::Bound<Integer, i32>,
+            >,
+          >,
+        >,
+        comment_like::table,
+        LeftOuter,
+      >,
+      diesel::expression::operators::And<
+        diesel::expression::operators::Eq<comment::columns::id, comment_like::columns::comment_id>,
+        diesel::expression::operators::Eq<
+          comment_like::columns::user_id,
+          diesel::expression::bound::Bound<Integer, i32>,
+        >,
+      >,
+    >,
+    Pg,
+  >;
+}
+
+pub struct CommentQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  query: join_types::BoxedCommentJoin<'a>,
+  listing_type: ListingType,
+  sort: &'a SortType,
+  for_community_id: Option<i32>,
+  for_community_name: Option<String>,
+  for_post_id: Option<i32>,
+  for_creator_id: Option<i32>,
+  for_recipient_id: Option<i32>,
+  search_term: Option<String>,
+  saved_only: bool,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> CommentQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection, my_user_id: Option<i32>) -> Self {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let query = comment::table
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    CommentQueryBuilder {
+      conn,
+      query,
+      listing_type: ListingType::All,
+      sort: &SortType::New,
+      for_community_id: None,
+      for_community_name: None,
+      for_post_id: None,
+      for_creator_id: None,
+      for_recipient_id: None,
+      search_term: None,
+      saved_only: false,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn listing_type(mut self, listing_type: ListingType) -> Self {
+    self.listing_type = listing_type;
+    self
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn for_post_id<T: MaybeOptional<i32>>(mut self, for_post_id: T) -> Self {
+    self.for_post_id = for_post_id.get_optional();
+    self
+  }
+
+  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
+    self.for_creator_id = for_creator_id.get_optional();
+    self
+  }
+
+  pub fn for_recipient_id<T: MaybeOptional<i32>>(mut self, for_recipient_id: T) -> Self {
+    self.for_creator_id = for_recipient_id.get_optional();
+    self
+  }
+
+  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
+    self.for_community_id = for_community_id.get_optional();
+    self
+  }
+
+  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
+    self.for_community_name = for_community_name.get_optional();
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn saved_only(mut self, saved_only: bool) -> Self {
+    self.saved_only = saved_only;
+    self
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<CommentView>, Error> {
+    use diesel::dsl::*;
+
+    let mut query = self.query;
+
+    // The replies
+    if let Some(for_recipient_id) = self.for_recipient_id {
+      query = query
+        // TODO needs lots of testing
+        .filter(user_alias_1::id.eq(for_recipient_id))
+        .filter(comment::deleted.eq(false))
+        .filter(comment::removed.eq(false));
+    }
+
+    if self.unread_only {
+      query = query.filter(comment::read.eq(false));
+    }
+
+    if let Some(for_creator_id) = self.for_creator_id {
+      query = query.filter(comment::creator_id.eq(for_creator_id));
+    };
+
+    if let Some(for_community_id) = self.for_community_id {
+      query = query.filter(post::community_id.eq(for_community_id));
+    }
+
+    if let Some(for_community_name) = self.for_community_name {
+      query = query
+        .filter(community::name.eq(for_community_name))
+        .filter(comment::local.eq(true));
+    }
+
+    if let Some(for_post_id) = self.for_post_id {
+      query = query.filter(comment::post_id.eq(for_post_id));
+    };
+
+    if let Some(search_term) = self.search_term {
+      query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
+    };
+
+    query = match self.listing_type {
+      // ListingType::Subscribed => query.filter(community_follower::subscribed.eq(true)),
+      ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
+      ListingType::Local => query.filter(community::local.eq(true)),
+      _ => query,
+    };
+
+    if self.saved_only {
+      query = query.filter(comment_saved::id.is_not_null());
+    }
+
+    query = match self.sort {
+      SortType::Hot | SortType::Active => query
+        .order_by(hot_rank(comment_aggregates::score, comment::published).desc())
+        .then_order_by(comment::published.desc()),
+      SortType::New => query.order_by(comment::published.desc()),
+      SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(comment::published.gt(now - 1.years()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(comment::published.gt(now - 1.months()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(comment::published.gt(now - 1.weeks()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(comment::published.gt(now - 1.days()))
+        .order_by(comment_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    // Note: deleted and removed comments are done on the front side
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .load::<CommentViewTuple>(self.conn)?;
+
+    Ok(CommentView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommentView {
+  type DbTuple = CommentViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        comment: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        recipient: a.3.to_owned(),
+        post: a.4.to_owned(),
+        community: a.5.to_owned(),
+        counts: a.6.to_owned(),
+        creator_banned_from_community: a.7.is_some(),
+        subscribed: a.8.is_some(),
+        saved: a.9.is_some(),
+        my_vote: a.10,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
index 465e5cffafa404e817f320d7819dcb57ccbeaf20..a3295ec004c00603757a59cfb34885a789b4b904 100644 (file)
@@ -1,3 +1,4 @@
+pub mod comment_view;
 pub mod community_follower_view;
 pub mod community_moderator_view;
 pub mod community_user_ban_view;
index 4888f9cfbc63f1bbe9d7c9696f5d72d2e5eaf0f1..9791d0a8b508d2450b89c6453b186a26f33f4820 100644 (file)
@@ -33,12 +33,12 @@ pub struct PostView {
   pub post: Post,
   pub creator: UserSafe,
   pub community: CommunitySafe,
-  pub counts: PostAggregates,
-  pub subscribed: bool,                    // Left join to CommunityFollower
   pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
-  pub saved: bool,                         // Left join to PostSaved
-  pub read: bool,                          // Left join to PostRead
-  pub my_vote: Option<i16>,                // Left join to PostLike
+  pub counts: PostAggregates,
+  pub subscribed: bool,     // Left join to CommunityFollower
+  pub saved: bool,          // Left join to PostSaved
+  pub read: bool,           // Left join to PostRead
+  pub my_vote: Option<i16>, // Left join to PostLike
 }
 
 type PostViewTuple = (
@@ -76,7 +76,7 @@ impl PostView {
         community_user_ban::table.on(
           post::community_id
             .eq(community_user_ban::community_id)
-            .and(community_user_ban::user_id.eq(community::creator_id)),
+            .and(community_user_ban::user_id.eq(post::creator_id)),
         ),
       )
       .inner_join(post_aggregates::table)
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/down.sql b/migrations/2020-12-14-020038_create_comment_aggregates/down.sql
new file mode 100644 (file)
index 0000000..6fd9ddc
--- /dev/null
@@ -0,0 +1,7 @@
+-- comment aggregates
+drop table comment_aggregates;
+drop trigger comment_aggregates_comment on comment;
+drop trigger comment_aggregates_score on comment_like;
+drop function 
+  comment_aggregates_comment,
+  comment_aggregates_score;
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/up.sql b/migrations/2020-12-14-020038_create_comment_aggregates/up.sql
new file mode 100644 (file)
index 0000000..1a168be
--- /dev/null
@@ -0,0 +1,82 @@
+-- Add comment aggregates
+create table comment_aggregates (
+  id serial primary key,
+  comment_id int references comment on update cascade on delete cascade not null,
+  score bigint not null default 0,
+  upvotes bigint not null default 0,
+  downvotes bigint not null default 0,
+  unique (comment_id)
+);
+
+insert into comment_aggregates (comment_id, score, upvotes, downvotes)
+  select 
+    c.id,
+    COALESCE(cl.total, 0::bigint) AS score,
+    COALESCE(cl.up, 0::bigint) AS upvotes,
+    COALESCE(cl.down, 0::bigint) AS downvotes
+  from comment c
+  left join ( select l.comment_id as id,
+    sum(l.score) as total,
+    count(
+      case
+      when l.score = 1 then 1
+      else null::integer
+      end) as up,
+    count(
+      case
+      when l.score = '-1'::integer then 1
+      else null::integer
+      end) as down
+    from comment_like l
+    group by l.comment_id) cl on cl.id = c.id;
+
+-- Add comment aggregate triggers
+
+-- initial comment add
+create function comment_aggregates_comment()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into comment_aggregates (comment_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from comment_aggregates where comment_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_comment
+after insert or delete on comment
+for each row
+execute procedure comment_aggregates_comment();
+
+-- comment score
+create function comment_aggregates_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update comment_aggregates ca
+    set score = score + NEW.score,
+    upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
+    downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
+    where ca.comment_id = NEW.comment_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to comment because that comment may not exist anymore
+    update comment_aggregates ca
+    set score = score - OLD.score,
+    upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
+    downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
+    from comment c
+    where ca.comment_id = c.id
+    and ca.comment_id = OLD.comment_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_score
+after insert or delete on comment_like
+for each row
+execute procedure comment_aggregates_score();