From efdcbc44c47ec540fa2977126bafda63bd3fed41 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Fri, 4 Dec 2020 16:35:46 -0500
Subject: [PATCH] Starting to work on community view, 2

---
 docs/src/about_goals.md                       |  1 +
 .../src/aggregates/community_aggregates.rs    | 21 +++++
 lemmy_db/src/aggregates/mod.rs                |  1 +
 lemmy_db/src/schema.rs                        | 12 +++
 lemmy_db/src/views/community_view.rs          | 23 ++---
 .../down.sql                                  |  9 ++
 .../up.sql                                    | 92 +++++++++++++++++++
 7 files changed, 143 insertions(+), 16 deletions(-)
 create mode 100644 lemmy_db/src/aggregates/community_aggregates.rs
 create mode 100644 migrations/2020-12-04-183345_create_community_aggregates/down.sql
 create mode 100644 migrations/2020-12-04-183345_create_community_aggregates/up.sql

diff --git a/docs/src/about_goals.md b/docs/src/about_goals.md
index e0427481..ea86db07 100644
--- a/docs/src/about_goals.md
+++ b/docs/src/about_goals.md
@@ -36,6 +36,7 @@
 - [Rust docker build](https://shaneutt.com/blog/rust-fast-small-docker-image-builds/)
 - [Zurb mentions](https://github.com/zurb/tribute)
 - [TippyJS](https://github.com/atomiks/tippyjs)
+- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html)
 
 ## Activitypub guides
 
diff --git a/lemmy_db/src/aggregates/community_aggregates.rs b/lemmy_db/src/aggregates/community_aggregates.rs
new file mode 100644
index 00000000..9a8ea365
--- /dev/null
+++ b/lemmy_db/src/aggregates/community_aggregates.rs
@@ -0,0 +1,21 @@
+use crate::schema::community_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "community_aggregates"]
+pub struct CommunityAggregates {
+  pub id: i32,
+  pub community_id: i32,
+  pub subscribers: i64,
+  pub posts: i64,
+  pub counts: i64,
+}
+
+impl CommunityAggregates {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    community_aggregates::table.find(id).first::<Self>(conn)
+  }
+}
+
+// TODO add unit tests, to make sure triggers are working
diff --git a/lemmy_db/src/aggregates/mod.rs b/lemmy_db/src/aggregates/mod.rs
index 2791c977..9f38f2ed 100644
--- a/lemmy_db/src/aggregates/mod.rs
+++ b/lemmy_db/src/aggregates/mod.rs
@@ -1,2 +1,3 @@
+pub mod community_aggregates;
 pub mod site_aggregates;
 pub mod user_aggregates;
diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs
index bc575f4e..e6dd6d4b 100644
--- a/lemmy_db/src/schema.rs
+++ b/lemmy_db/src/schema.rs
@@ -127,6 +127,16 @@ table! {
     }
 }
 
+table! {
+    community_aggregates (id) {
+        id -> Int4,
+        community_id -> Int4,
+        subscribers -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+    }
+}
+
 table! {
     community_aggregates_fast (id) {
         id -> Int4,
@@ -544,6 +554,7 @@ joinable!(comment_saved -> comment (comment_id));
 joinable!(comment_saved -> user_ (user_id));
 joinable!(community -> category (category_id));
 joinable!(community -> user_ (creator_id));
+joinable!(community_aggregates -> community (community_id));
 joinable!(community_follower -> community (community_id));
 joinable!(community_follower -> user_ (user_id));
 joinable!(community_moderator -> community (community_id));
@@ -587,6 +598,7 @@ allow_tables_to_appear_in_same_query!(
   comment_report,
   comment_saved,
   community,
+  community_aggregates,
   community_aggregates_fast,
   community_follower,
   community_moderator,
diff --git a/lemmy_db/src/views/community_view.rs b/lemmy_db/src/views/community_view.rs
index c7b9b398..4e0b5882 100644
--- a/lemmy_db/src/views/community_view.rs
+++ b/lemmy_db/src/views/community_view.rs
@@ -1,7 +1,8 @@
 use crate::{
+  aggregates::community_aggregates::CommunityAggregates,
   category::Category,
   community::{Community, CommunityFollower},
-  schema::{category, community, community_follower, user_},
+  schema::{category, community, community_aggregates, community_follower, user_},
   user::{UserSafe, User_},
 };
 use diesel::{result::Error, *};
@@ -13,21 +14,9 @@ pub struct CommunityView {
   pub creator: UserSafe,
   pub category: Category,
   pub subscribed: bool,
+  pub counts: CommunityAggregates,
 }
 
-// creator_actor_id -> Text,
-// creator_local -> Bool,
-// creator_name -> Varchar,
-// creator_preferred_username -> Nullable<Varchar>,
-// creator_avatar -> Nullable<Text>,
-// category_name -> Varchar,
-// number_of_subscribers -> BigInt,
-// number_of_posts -> BigInt,
-// number_of_comments -> BigInt,
-// hot_rank -> Int4,
-// user_id -> Nullable<Int4>,
-// subscribed -> Nullable<Bool>,
-
 impl CommunityView {
   pub fn read(
     conn: &PgConnection,
@@ -45,17 +34,19 @@ impl CommunityView {
       None => false,
     };
 
-    let (community, creator, category) = community::table
+    let (community, creator, category, counts) = community::table
       .find(community_id)
       .inner_join(user_::table)
       .inner_join(category::table)
-      .first::<(Community, User_, Category)>(conn)?;
+      .inner_join(community_aggregates::table)
+      .first::<(Community, User_, Category, CommunityAggregates)>(conn)?;
 
     Ok(CommunityView {
       community,
       creator: creator.to_safe(),
       category,
       subscribed,
+      counts,
     })
   }
 }
diff --git a/migrations/2020-12-04-183345_create_community_aggregates/down.sql b/migrations/2020-12-04-183345_create_community_aggregates/down.sql
new file mode 100644
index 00000000..ac2872d1
--- /dev/null
+++ b/migrations/2020-12-04-183345_create_community_aggregates/down.sql
@@ -0,0 +1,9 @@
+-- community aggregates
+drop table community_aggregates;
+drop trigger community_aggregates_post_count on post;
+drop trigger community_aggregates_comment_count on comment;
+drop trigger community_aggregates_subscriber_count on community_follower;
+drop function 
+  community_aggregates_post_count,
+  community_aggregates_comment_count,
+  community_aggregates_subscriber_count;
diff --git a/migrations/2020-12-04-183345_create_community_aggregates/up.sql b/migrations/2020-12-04-183345_create_community_aggregates/up.sql
new file mode 100644
index 00000000..8af01597
--- /dev/null
+++ b/migrations/2020-12-04-183345_create_community_aggregates/up.sql
@@ -0,0 +1,92 @@
+-- Add community aggregates
+create table community_aggregates (
+  id serial primary key,
+  community_id int references community on update cascade on delete cascade not null,
+  subscribers bigint not null,
+  posts bigint not null,
+  comments bigint not null,
+  unique (community_id)
+);
+
+insert into community_aggregates (community_id, subscribers, posts, comments)
+  select 
+    c.id,
+    coalesce(cf.subs, 0::bigint) as subscribers,
+    coalesce(cd.posts, 0::bigint) as posts,
+    coalesce(cd.comments, 0::bigint) as comments
+  from community c
+  left join ( 
+    select 
+      p.community_id,
+      count(distinct p.id) as posts,
+      count(distinct ct.id) as comments
+    from post p
+    left join comment ct on p.id = ct.post_id
+    group by p.community_id
+  ) cd on cd.community_id = c.id
+  left join ( 
+    select 
+      community_follower.community_id,
+      count(*) as subs
+    from community_follower
+    group by community_follower.community_id
+  ) cf on cf.community_id = c.id;
+
+-- Add community aggregate triggers
+-- post count
+create function community_aggregates_post_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates 
+    set posts = posts + 1 where community_id = NEW.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set posts = posts - 1 where community_id = OLD.community_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_post_count
+after insert or delete on post
+execute procedure community_aggregates_post_count();
+
+-- comment count
+create function community_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates 
+    set comments = comments + 1 where community_id = NEW.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set comments = comments - 1 where community_id = OLD.community_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_comment_count
+after insert or delete on comment
+execute procedure community_aggregates_comment_count();
+
+-- subscriber count
+create function community_aggregates_subscriber_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update community_aggregates 
+    set subscribers = subscribers + 1 where community_id = NEW.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set subscribers = subscribers - 1 where community_id = OLD.community_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_subscriber_count
+after insert or delete on community_follower
+execute procedure community_aggregates_subscriber_count();
+
-- 
2.44.1