From 77314796070ad97804d9e47ef0460c69153f79d1 Mon Sep 17 00:00:00 2001
From: Dessalines <tyhou13@gmx.com>
Date: Wed, 2 Dec 2020 22:39:31 -0500
Subject: [PATCH] Adding SiteAggregates.

---
 lemmy_api/src/site.rs                         |  17 +--
 .../src/activities/receive/private_message.rs |   2 +-
 lemmy_apub/src/inbox/community_inbox.rs       |   2 +-
 lemmy_apub/src/inbox/mod.rs                   |   6 +-
 lemmy_apub/src/inbox/shared_inbox.rs          |   2 +-
 lemmy_apub/src/inbox/user_inbox.rs            |   4 +-
 lemmy_db/src/lib.rs                           |   2 +-
 lemmy_db/src/schema.rs                        |  11 ++
 lemmy_db/src/site_aggregates.rs               |  19 +++
 lemmy_db/src/site_view.rs                     |  55 --------
 lemmy_db/src/views/mod.rs                     |   1 +
 lemmy_db/src/views/user_view.rs               |  65 +++++++++
 lemmy_structs/src/site.rs                     |   6 +-
 .../down.sql                                  |  19 +++
 .../up.sql                                    | 124 ++++++++++++++++++
 .../2020-12-02-152437_remove_views/down.sql   |   1 -
 .../2020-12-02-152437_remove_views/up.sql     |   1 -
 17 files changed, 257 insertions(+), 80 deletions(-)
 create mode 100644 lemmy_db/src/site_aggregates.rs
 delete mode 100644 lemmy_db/src/site_view.rs
 create mode 100644 lemmy_db/src/views/user_view.rs
 create mode 100644 migrations/2020-12-02-152437_create_site_aggregates/down.sql
 create mode 100644 migrations/2020-12-02-152437_create_site_aggregates/up.sql
 delete mode 100644 migrations/2020-12-02-152437_remove_views/down.sql
 delete mode 100644 migrations/2020-12-02-152437_remove_views/up.sql

diff --git a/lemmy_api/src/site.rs b/lemmy_api/src/site.rs
index e8d0df04..2cd97f7f 100644
--- a/lemmy_api/src/site.rs
+++ b/lemmy_api/src/site.rs
@@ -19,6 +19,7 @@ use lemmy_db::{
   naive_now,
   post_view::*,
   site::*,
+  site_aggregates::SiteAggregates,
   user_view::*,
   views::site_view::SiteView,
   Crud,
@@ -310,6 +311,8 @@ impl Perform for GetSite {
         u
       });
 
+    let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
+
     Ok(GetSiteResponse {
       site: site_view,
       admins,
@@ -318,11 +321,7 @@ impl Perform for GetSite {
       version: version::VERSION.to_string(),
       my_user,
       federated_instances: linked_instances(context.pool()).await?,
-      // TODO
-      number_of_users: 0,
-      number_of_posts: 0,
-      number_of_comments: 0,
-      number_of_communities: 0,
+      counts,
     })
   }
 }
@@ -546,6 +545,8 @@ impl Perform for TransferSite {
 
     let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
 
+    let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
+
     Ok(GetSiteResponse {
       site: Some(site_view),
       admins,
@@ -554,11 +555,7 @@ impl Perform for TransferSite {
       version: version::VERSION.to_string(),
       my_user: Some(user),
       federated_instances: linked_instances(context.pool()).await?,
-      // TODO
-      number_of_users: 0,
-      number_of_posts: 0,
-      number_of_comments: 0,
-      number_of_communities: 0,
+      counts,
     })
   }
 }
diff --git a/lemmy_apub/src/activities/receive/private_message.rs b/lemmy_apub/src/activities/receive/private_message.rs
index 8f1c95b9..07913226 100644
--- a/lemmy_apub/src/activities/receive/private_message.rs
+++ b/lemmy_apub/src/activities/receive/private_message.rs
@@ -194,7 +194,7 @@ async fn check_private_message_activity_valid<T, Kind>(
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   if to_and_cc.len() != 1 {
     return Err(anyhow!("Private message can only be addressed to one user").into());
   }
diff --git a/lemmy_apub/src/inbox/community_inbox.rs b/lemmy_apub/src/inbox/community_inbox.rs
index 7c144a00..14878cfe 100644
--- a/lemmy_apub/src/inbox/community_inbox.rs
+++ b/lemmy_apub/src/inbox/community_inbox.rs
@@ -81,7 +81,7 @@ pub async fn community_inbox(
     Community::read_from_name(&conn, &path)
   })
   .await??;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   if !to_and_cc.contains(&&community.actor_id()?) {
     return Err(anyhow!("Activity delivered to wrong community").into());
   }
diff --git a/lemmy_apub/src/inbox/mod.rs b/lemmy_apub/src/inbox/mod.rs
index ce6c7ede..f8dd8bfe 100644
--- a/lemmy_apub/src/inbox/mod.rs
+++ b/lemmy_apub/src/inbox/mod.rs
@@ -50,7 +50,7 @@ pub(crate) async fn is_activity_already_known(
   }
 }
 
-pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Result<Vec<Url>, LemmyError>
+pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Vec<Url>
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
@@ -75,14 +75,14 @@ where
       .collect();
     to_and_cc.append(&mut cc);
   }
-  Ok(to_and_cc)
+  to_and_cc
 }
 
 pub(crate) fn is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
 where
   T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
 {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   if to_and_cc.contains(&public()) {
     Ok(())
   } else {
diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs
index 2875696e..e9a81ab3 100644
--- a/lemmy_apub/src/inbox/shared_inbox.rs
+++ b/lemmy_apub/src/inbox/shared_inbox.rs
@@ -66,7 +66,7 @@ pub async fn shared_inbox(
 
   let activity_any_base = activity.clone().into_any_base()?;
   let mut res: Option<HttpResponse> = None;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   // Handle community first, so in case the sender is banned by the community, it will error out.
   // If we handled the user receive first, the activity would be inserted to the database before the
   // community could check for bans.
diff --git a/lemmy_apub/src/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs
index 2f847a5c..cc1c0661 100644
--- a/lemmy_apub/src/inbox/user_inbox.rs
+++ b/lemmy_apub/src/inbox/user_inbox.rs
@@ -101,7 +101,7 @@ pub async fn user_inbox(
     User_::read_from_name(&conn, &username)
   })
   .await??;
-  let to_and_cc = get_activity_to_and_cc(&activity)?;
+  let to_and_cc = get_activity_to_and_cc(&activity);
   // TODO: we should also accept activities that are sent to community followers
   if !to_and_cc.contains(&&user.actor_id()?) {
     return Err(anyhow!("Activity delivered to wrong user").into());
@@ -172,7 +172,7 @@ async fn is_for_user_inbox(
   context: &LemmyContext,
   activity: &UserAcceptedActivities,
 ) -> Result<(), LemmyError> {
-  let to_and_cc = get_activity_to_and_cc(activity)?;
+  let to_and_cc = get_activity_to_and_cc(activity);
   // Check if it is addressed directly to any local user
   if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
     return Ok(());
diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs
index bf291db3..c7f4585f 100644
--- a/lemmy_db/src/lib.rs
+++ b/lemmy_db/src/lib.rs
@@ -28,7 +28,7 @@ pub mod private_message;
 pub mod private_message_view;
 pub mod schema;
 pub mod site;
-pub mod site_view;
+pub mod site_aggregates;
 pub mod user;
 pub mod user_mention;
 pub mod user_mention_view;
diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs
index 49bbc46f..ce84c7d5 100644
--- a/lemmy_db/src/schema.rs
+++ b/lemmy_db/src/schema.rs
@@ -440,6 +440,16 @@ table! {
     }
 }
 
+table! {
+    site_aggregates (id) {
+        id -> Int4,
+        users -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+        communities -> Int8,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -587,6 +597,7 @@ allow_tables_to_appear_in_same_query!(
   post_saved,
   private_message,
   site,
+  site_aggregates,
   user_,
   user_ban,
   user_fast,
diff --git a/lemmy_db/src/site_aggregates.rs b/lemmy_db/src/site_aggregates.rs
new file mode 100644
index 00000000..488046ac
--- /dev/null
+++ b/lemmy_db/src/site_aggregates.rs
@@ -0,0 +1,19 @@
+use crate::schema::site_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "site_aggregates"]
+pub struct SiteAggregates {
+  pub id: i32,
+  pub users: i64,
+  pub posts: i64,
+  pub comments: i64,
+  pub communities: i64,
+}
+
+impl SiteAggregates {
+  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
+    site_aggregates::table.first::<Self>(conn)
+  }
+}
diff --git a/lemmy_db/src/site_view.rs b/lemmy_db/src/site_view.rs
deleted file mode 100644
index fd15ac77..00000000
--- a/lemmy_db/src/site_view.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use diesel::{result::Error, *};
-use serde::Serialize;
-
-table! {
-  site_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    description -> Nullable<Text>,
-    creator_id -> Int4,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    enable_downvotes -> Bool,
-    open_registration -> Bool,
-    enable_nsfw -> Bool,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    number_of_users -> BigInt,
-    number_of_posts -> BigInt,
-    number_of_comments -> BigInt,
-    number_of_communities -> BigInt,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "site_view"]
-pub struct SiteView {
-  pub id: i32,
-  pub name: String,
-  pub description: Option<String>,
-  pub creator_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub enable_downvotes: bool,
-  pub open_registration: bool,
-  pub enable_nsfw: bool,
-  pub icon: Option<String>,
-  pub banner: Option<String>,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub number_of_users: i64,
-  pub number_of_posts: i64,
-  pub number_of_comments: i64,
-  pub number_of_communities: i64,
-}
-
-impl SiteView {
-  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
-    use super::site_view::site_view::dsl::*;
-    site_view.first::<Self>(conn)
-  }
-}
diff --git a/lemmy_db/src/views/mod.rs b/lemmy_db/src/views/mod.rs
index 41fabde8..cd68cfe0 100644
--- a/lemmy_db/src/views/mod.rs
+++ b/lemmy_db/src/views/mod.rs
@@ -1 +1,2 @@
 pub mod site_view;
+pub mod user_view;
diff --git a/lemmy_db/src/views/user_view.rs b/lemmy_db/src/views/user_view.rs
new file mode 100644
index 00000000..eb18afbc
--- /dev/null
+++ b/lemmy_db/src/views/user_view.rs
@@ -0,0 +1,65 @@
+use crate::{
+  schema::user_,
+  user::{UserSafe, User_},
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewSafe {
+  pub user: UserSafe,
+  // TODO
+  // pub number_of_posts: i64,
+  // pub post_score: i64,
+  // pub number_of_comments: i64,
+  // pub comment_score: i64,
+}
+
+pub struct UserViewDangerous {
+  pub user: User_,
+  // TODO
+  // pub number_of_posts: i64,
+  // pub post_score: i64,
+  // pub number_of_comments: i64,
+  // pub comment_score: i64,
+}
+
+impl UserViewDangerous {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let user = user_::table.find(id).first::<User_>(conn)?;
+    Ok(Self { user })
+  }
+}
+
+impl UserViewSafe {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let user = user_::table.find(id).first::<User_>(conn)?.to_safe();
+    Ok(Self { user })
+  }
+
+  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let admins = user_::table
+      // TODO do joins here
+      .filter(user_::admin.eq(true))
+      .order_by(user_::published)
+      .load::<User_>(conn)?;
+
+    Ok(vec_to_user_view_safe(admins))
+  }
+
+  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let banned = user_::table
+      // TODO do joins here
+      .filter(user_::banned.eq(true))
+      .load::<User_>(conn)?;
+
+    Ok(vec_to_user_view_safe(banned))
+  }
+}
+
+fn vec_to_user_view_safe(users: Vec<User_>) -> Vec<UserViewSafe> {
+  users
+    .iter()
+    .map(|a| UserViewSafe { user: a.to_safe() })
+    .collect::<Vec<UserViewSafe>>()
+}
diff --git a/lemmy_structs/src/site.rs b/lemmy_structs/src/site.rs
index 2192c4fd..12fda258 100644
--- a/lemmy_structs/src/site.rs
+++ b/lemmy_structs/src/site.rs
@@ -4,6 +4,7 @@ use lemmy_db::{
   community_view::*,
   moderator_views::*,
   post_view::*,
+  site_aggregates::SiteAggregates,
   user::*,
   user_view::*,
   views::site_view::SiteView,
@@ -98,10 +99,7 @@ pub struct SiteResponse {
 #[derive(Serialize)]
 pub struct GetSiteResponse {
   pub site: Option<SiteView>, // Because the site might not be set up yet
-  pub number_of_users: i64,
-  pub number_of_posts: i64,
-  pub number_of_comments: i64,
-  pub number_of_communities: i64,
+  pub counts: SiteAggregates,
   pub admins: Vec<UserView>,
   pub banned: Vec<UserView>,
   pub online: usize,
diff --git a/migrations/2020-12-02-152437_create_site_aggregates/down.sql b/migrations/2020-12-02-152437_create_site_aggregates/down.sql
new file mode 100644
index 00000000..bd90603d
--- /dev/null
+++ b/migrations/2020-12-02-152437_create_site_aggregates/down.sql
@@ -0,0 +1,19 @@
+-- Site aggregates
+drop table site_aggregates;
+drop trigger site_aggregates_insert_user on user_;
+drop trigger site_aggregates_delete_user on user_;
+drop trigger site_aggregates_insert_post on post;
+drop trigger site_aggregates_delete_post on post;
+drop trigger site_aggregates_insert_comment on comment;
+drop trigger site_aggregates_delete_comment on comment;
+drop trigger site_aggregates_insert_community on community;
+drop trigger site_aggregates_delete_community on community;
+drop function 
+  site_aggregates_user_increment,
+  site_aggregates_user_decrement,
+  site_aggregates_post_increment,
+  site_aggregates_post_decrement,
+  site_aggregates_comment_increment,
+  site_aggregates_comment_decrement,
+  site_aggregates_community_increment,
+  site_aggregates_community_decrement;
diff --git a/migrations/2020-12-02-152437_create_site_aggregates/up.sql b/migrations/2020-12-02-152437_create_site_aggregates/up.sql
new file mode 100644
index 00000000..7f482268
--- /dev/null
+++ b/migrations/2020-12-02-152437_create_site_aggregates/up.sql
@@ -0,0 +1,124 @@
+-- Add site aggregates
+create table site_aggregates (
+  id serial primary key,
+  users bigint not null,
+  posts bigint not null,
+  comments bigint not null,
+  communities bigint not null
+);
+
+insert into site_aggregates (users, posts, comments, communities)
+  select ( select coalesce(count(*), 0) from user_) as users, 
+  ( select coalesce(count(*), 0) from post) as posts,
+  ( select coalesce(count(*), 0) from comment) as comments,
+  ( select coalesce(count(*), 0) from community) as communities;
+
+-- Add site aggregate triggers
+-- user
+create function site_aggregates_user_increment()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set users = users + 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_insert_user
+after insert on user_
+execute procedure site_aggregates_user_increment();
+
+create function site_aggregates_user_decrement()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set users = users - 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_delete_user
+after delete on user_
+execute procedure site_aggregates_user_decrement();
+
+-- post
+create function site_aggregates_post_increment()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set posts = posts + 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_insert_post
+after insert on post
+execute procedure site_aggregates_post_increment();
+
+create function site_aggregates_post_decrement()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set posts = posts - 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_delete_post
+after delete on post
+execute procedure site_aggregates_post_decrement();
+
+-- comment
+create function site_aggregates_comment_increment()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set comments = comments + 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_insert_comment
+after insert on comment
+execute procedure site_aggregates_comment_increment();
+
+create function site_aggregates_comment_decrement()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set comments = comments - 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_delete_comment
+after delete on comment
+execute procedure site_aggregates_comment_decrement();
+
+-- community
+create function site_aggregates_community_increment()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set communities = communities + 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_insert_community
+after insert on community
+execute procedure site_aggregates_community_increment();
+
+create function site_aggregates_community_decrement()
+returns trigger language plpgsql
+as $$
+begin
+  update site_aggregates 
+  set communities = communities - 1;
+  return null;
+end $$;
+
+create trigger site_aggregates_delete_community
+after delete on community
+execute procedure site_aggregates_community_decrement();
+
diff --git a/migrations/2020-12-02-152437_remove_views/down.sql b/migrations/2020-12-02-152437_remove_views/down.sql
deleted file mode 100644
index 291a97c5..00000000
--- a/migrations/2020-12-02-152437_remove_views/down.sql
+++ /dev/null
@@ -1 +0,0 @@
--- This file should undo anything in `up.sql`
\ No newline at end of file
diff --git a/migrations/2020-12-02-152437_remove_views/up.sql b/migrations/2020-12-02-152437_remove_views/up.sql
deleted file mode 100644
index 33cf74b5..00000000
--- a/migrations/2020-12-02-152437_remove_views/up.sql
+++ /dev/null
@@ -1 +0,0 @@
--- Your SQL goes here
\ No newline at end of file
-- 
2.44.1