]> Untitled Git - lemmy.git/commitdiff
Merge branch 'main' into move_views_to_diesel
authorDessalines <tyhou13@gmx.com>
Tue, 8 Dec 2020 18:17:55 +0000 (13:17 -0500)
committerDessalines <tyhou13@gmx.com>
Tue, 8 Dec 2020 18:17:55 +0000 (13:17 -0500)
50 files changed:
docs/src/about/goals.md
lemmy_api/src/comment.rs
lemmy_api/src/community.rs
lemmy_api/src/lib.rs
lemmy_api/src/post.rs
lemmy_api/src/site.rs
lemmy_api/src/user.rs
lemmy_apub/src/activities/receive/community.rs
lemmy_apub/src/activities/receive/private_message.rs
lemmy_apub/src/activities/send/community.rs
lemmy_apub/src/fetcher.rs
lemmy_apub/src/http/community.rs
lemmy_apub/src/inbox/community_inbox.rs
lemmy_apub/src/inbox/mod.rs
lemmy_apub/src/inbox/shared_inbox.rs
lemmy_apub/src/inbox/user_inbox.rs
lemmy_apub/src/objects/community.rs
lemmy_db/src/aggregates/community_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/mod.rs [new file with mode: 0644]
lemmy_db/src/aggregates/site_aggregates.rs [new file with mode: 0644]
lemmy_db/src/aggregates/user_aggregates.rs [new file with mode: 0644]
lemmy_db/src/category.rs
lemmy_db/src/comment.rs
lemmy_db/src/community.rs
lemmy_db/src/community_view.rs [deleted file]
lemmy_db/src/lib.rs
lemmy_db/src/schema.rs
lemmy_db/src/site.rs
lemmy_db/src/site_view.rs [deleted file]
lemmy_db/src/user.rs
lemmy_db/src/user_view.rs [deleted file]
lemmy_db/src/views/community_follower_view.rs [new file with mode: 0644]
lemmy_db/src/views/community_moderator_view.rs [new file with mode: 0644]
lemmy_db/src/views/community_user_ban_view.rs [new file with mode: 0644]
lemmy_db/src/views/community_view.rs [new file with mode: 0644]
lemmy_db/src/views/mod.rs [new file with mode: 0644]
lemmy_db/src/views/site_view.rs [new file with mode: 0644]
lemmy_db/src/views/user_view.rs [new file with mode: 0644]
lemmy_structs/src/community.rs
lemmy_structs/src/post.rs
lemmy_structs/src/site.rs
lemmy_structs/src/user.rs
migrations/2020-12-02-152437_create_site_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-02-152437_create_site_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-03-035643_create_user_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-03-035643_create_user_aggregates/up.sql [new file with mode: 0644]
migrations/2020-12-04-183345_create_community_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-04-183345_create_community_aggregates/up.sql [new file with mode: 0644]
src/routes/feeds.rs
src/routes/nodeinfo.rs

index e7d8e8dc6b2a72f51a8c737481c9dfe9a3305e91..0ce019fcfd69938032207012a06eba766cca0b74 100644 (file)
@@ -36,3 +36,4 @@
 - [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)
index e74fa808e6ef30ce76e074156d0a3b0463cf213d..5ad62f146f2ae498ebeb7e9ba512bbe03fec2ac2 100644 (file)
@@ -15,8 +15,8 @@ use lemmy_db::{
   comment_view::*,
   moderator::*,
   post::*,
-  site_view::*,
   user::*,
+  views::site_view::SiteView,
   Crud,
   Likeable,
   ListingType,
@@ -552,8 +552,8 @@ impl Perform for CreateCommentLike {
 
     // Don't do a downvote if site has downvotes disabled
     if data.score == -1 {
-      let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
-      if !site.enable_downvotes {
+      let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
+      if !site_view.site.enable_downvotes {
         return Err(APIError::err("downvotes_disabled").into());
       }
     }
index d7de0e6bd717a3bab6ad626599c846c08e5dfe26..fe7748f945f9fcdafe750d51cc517dd9bb21daa9 100644 (file)
@@ -13,13 +13,17 @@ use lemmy_db::{
   comment::Comment,
   comment_view::CommentQueryBuilder,
   community::*,
-  community_view::*,
   diesel_option_overwrite,
   moderator::*,
   naive_now,
   post::Post,
   site::*,
-  user_view::*,
+  views::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    community_view::{CommunityQueryBuilder, CommunityView},
+    user_view::UserViewSafe,
+  },
   ApubObject,
   Bannable,
   Crud,
@@ -96,7 +100,7 @@ impl Perform for GetCommunity {
       .unwrap_or(1);
 
     let res = GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online,
     };
@@ -203,9 +207,7 @@ impl Perform for CreateCommunity {
     })
     .await??;
 
-    Ok(CommunityResponse {
-      community: community_view,
-    })
+    Ok(CommunityResponse { community_view })
   }
 }
 
@@ -228,7 +230,7 @@ impl Perform for EditCommunity {
     let edit_id = data.edit_id;
     let mods: Vec<i32> = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, edit_id)
-        .map(|v| v.into_iter().map(|m| m.user_id).collect())
+        .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
     })
     .await??;
     if !mods.contains(&user.id) {
@@ -285,9 +287,7 @@ impl Perform for EditCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
 
@@ -341,9 +341,7 @@ impl Perform for DeleteCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
 
@@ -409,9 +407,7 @@ impl Perform for RemoveCommunity {
     })
     .await??;
 
-    let res = CommunityResponse {
-      community: community_view,
-    };
+    let res = CommunityResponse { community_view };
 
     send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
 
@@ -446,9 +442,8 @@ impl Perform for ListCommunities {
     let page = data.page;
     let limit = data.limit;
     let communities = blocking(context.pool(), move |conn| {
-      CommunityQueryBuilder::create(conn)
+      CommunityQueryBuilder::create(conn, user_id)
         .sort(&sort)
-        .for_user(user_id)
         .show_nsfw(show_nsfw)
         .page(page)
         .limit(limit)
@@ -520,12 +515,10 @@ impl Perform for FollowCommunity {
     // For now, just assume that remote follows are accepted.
     // Otherwise, the subscribed will be null
     if !community.local {
-      community_view.subscribed = Some(data.follow);
+      community_view.subscribed = data.follow;
     }
 
-    Ok(CommunityResponse {
-      community: community_view,
-    })
+    Ok(CommunityResponse { community_view })
   }
 }
 
@@ -641,12 +634,12 @@ impl Perform for BanFromCommunity {
 
     let user_id = data.user_id;
     let user_view = blocking(context.pool(), move |conn| {
-      UserView::get_user_secure(conn, user_id)
+      UserViewSafe::read(conn, user_id)
     })
     .await??;
 
     let res = BanFromCommunityResponse {
-      user: user_view,
+      user_view,
       banned: data.ban,
     };
 
@@ -749,17 +742,19 @@ impl Perform for TransferCommunity {
     })
     .await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
 
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_creator_id)
+      .position(|r| r.user.id == site_creator_id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
 
     // Make sure user is the creator, or an admin
-    if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
+    if user.id != read_community.creator_id
+      && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
+    {
       return Err(APIError::err("not_an_admin").into());
     }
 
@@ -778,7 +773,7 @@ impl Perform for TransferCommunity {
     .await??;
     let creator_index = community_mods
       .iter()
-      .position(|r| r.user_id == data.user_id)
+      .position(|r| r.moderator.id == data.user_id)
       .context(location_info!())?;
     let creator_user = community_mods.remove(creator_index);
     community_mods.insert(0, creator_user);
@@ -792,8 +787,8 @@ impl Perform for TransferCommunity {
     // TODO: this should probably be a bulk operation
     for cmod in &community_mods {
       let community_moderator_form = CommunityModeratorForm {
-        community_id: cmod.community_id,
-        user_id: cmod.user_id,
+        community_id: cmod.community.id,
+        user_id: cmod.moderator.id,
       };
 
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
@@ -837,7 +832,7 @@ impl Perform for TransferCommunity {
 
     // Return the jwt
     Ok(GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online: 0,
     })
@@ -850,15 +845,16 @@ fn send_community_websocket(
   websocket_id: Option<ConnectionId>,
   op: UserOperation,
 ) {
+  // TODO is there any way around this?
   // Strip out the user id and subscribed when sending to others
-  let mut res_sent = res.clone();
-  res_sent.community.user_id = None;
-  res_sent.community.subscribed = None;
+  // let mut res_sent = res.clone();
+  // res_sent.community_view.user_id = None;
+  // res_sent.community.subscribed = None;
 
   context.chat_server().do_send(SendCommunityRoomMessage {
     op,
-    response: res_sent,
-    community_id: res.community.id,
+    response: res.to_owned(),
+    community_id: res.community_view.community.id,
     websocket_id,
   });
 }
index 06b629c772cd78e592c91ba802e67f603d93f46e..13998dc4dc98489c63b316a2b023d3bd63371a0f 100644 (file)
@@ -2,9 +2,9 @@ use crate::claims::Claims;
 use actix_web::{web, web::Data};
 use lemmy_db::{
   community::{Community, CommunityModerator},
-  community_view::CommunityUserBanView,
   post::Post,
   user::User_,
+  views::community_user_ban_view::CommunityUserBanView,
   Crud,
   DbPool,
 };
index 298076f75bbdcf1dd4dc9d9981b9e7d36d98ef31..89bb0fa87555f439a6720aec89be33eaf0ab7093 100644 (file)
@@ -11,13 +11,16 @@ use actix_web::web::Data;
 use lemmy_apub::{ApubLikeableType, ApubObjectType};
 use lemmy_db::{
   comment_view::*,
-  community_view::*,
   moderator::*,
   naive_now,
   post::*,
   post_report::*,
   post_view::*,
-  site_view::*,
+  views::{
+    community_moderator_view::CommunityModeratorView,
+    community_view::CommunityView,
+    site_view::SiteView,
+  },
   Crud,
   Likeable,
   ListingType,
@@ -281,8 +284,8 @@ impl Perform for CreatePostLike {
 
     // Don't do a downvote if site has downvotes disabled
     if data.score == -1 {
-      let site = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
-      if !site.enable_downvotes {
+      let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
+      if !site_view.site.enable_downvotes {
         return Err(APIError::err("downvotes_disabled").into());
       }
     }
index e4b1dd21330a8508eda289b448c7243555e24422..d865a8f811939f2644c7ca790a97619c759df8e3 100644 (file)
@@ -10,17 +10,20 @@ use actix_web::web::Data;
 use anyhow::Context;
 use lemmy_apub::fetcher::search_by_apub_id;
 use lemmy_db::{
+  aggregates::site_aggregates::SiteAggregates,
   category::*,
   comment_view::*,
-  community_view::*,
   diesel_option_overwrite,
   moderator::*,
   moderator_views::*,
   naive_now,
   post_view::*,
   site::*,
-  site_view::*,
-  user_view::*,
+  views::{
+    community_view::CommunityQueryBuilder,
+    site_view::SiteView,
+    user_view::{UserQueryBuilder, UserViewSafe},
+  },
   Crud,
   SearchType,
   SortType,
@@ -280,20 +283,20 @@ impl Perform for GetSite {
       None
     };
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
 
     // Make sure the site creator is the top admin
     if let Some(site_view) = site_view.to_owned() {
-      let site_creator_id = site_view.creator_id;
+      let site_creator_id = site_view.creator.id;
       // TODO investigate why this is sometimes coming back null
       // Maybe user_.admin isn't being set to true?
-      if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
+      if let Some(creator_index) = admins.iter().position(|r| r.user.id == site_creator_id) {
         let creator_user = admins.remove(creator_index);
         admins.insert(0, creator_user);
       }
     }
 
-    let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
+    let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
 
     let online = context
       .chat_server()
@@ -310,6 +313,8 @@ impl Perform for GetSite {
         u
       });
 
+    let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
+
     Ok(GetSiteResponse {
       site: site_view,
       admins,
@@ -318,6 +323,7 @@ impl Perform for GetSite {
       version: version::VERSION.to_string(),
       my_user,
       federated_instances: linked_instances(context.pool()).await?,
+      counts,
     })
   }
 }
@@ -386,7 +392,7 @@ impl Perform for Search {
       }
       SearchType::Communities => {
         communities = blocking(context.pool(), move |conn| {
-          CommunityQueryBuilder::create(conn)
+          CommunityQueryBuilder::create(conn, None)
             .sort(&sort)
             .search_term(q)
             .page(page)
@@ -439,7 +445,7 @@ impl Perform for Search {
         let sort = SortType::from_str(&data.sort)?;
 
         communities = blocking(context.pool(), move |conn| {
-          CommunityQueryBuilder::create(conn)
+          CommunityQueryBuilder::create(conn, None)
             .sort(&sort)
             .search_term(q)
             .page(page)
@@ -531,15 +537,17 @@ impl Perform for TransferSite {
 
     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_view.creator_id)
+      .position(|r| r.user.id == site_view.creator.id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
 
-    let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
+    let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
+
+    let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
 
     Ok(GetSiteResponse {
       site: Some(site_view),
@@ -549,6 +557,7 @@ impl Perform for TransferSite {
       version: version::VERSION.to_string(),
       my_user: Some(user),
       federated_instances: linked_instances(context.pool()).await?,
+      counts,
     })
   }
 }
@@ -587,8 +596,8 @@ impl Perform for SaveSiteConfig {
     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
 
     // Only let admins read this
-    let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
-    let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
+    let admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
+    let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.user.id).collect();
 
     if !admin_ids.contains(&user.id) {
       return Err(APIError::err("not_an_admin").into());
index 0d96c2a2f1d9c178a44978b558eca8ce9b807495..e3f447b7c2e8d7a0be3e39a35dfcb02fae39de6f 100644 (file)
@@ -19,7 +19,6 @@ use lemmy_db::{
   comment_report::CommentReportView,
   comment_view::*,
   community::*,
-  community_view::*,
   diesel_option_overwrite,
   moderator::*,
   naive_now,
@@ -30,11 +29,15 @@ use lemmy_db::{
   private_message::*,
   private_message_view::*,
   site::*,
-  site_view::*,
   user::*,
   user_mention::*,
   user_mention_view::*,
-  user_view::*,
+  views::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    site_view::SiteView,
+    user_view::{UserViewDangerous, UserViewSafe},
+  },
   Crud,
   Followable,
   Joinable,
@@ -113,9 +116,8 @@ impl Perform for Register {
     let data: &Register = &self;
 
     // Make sure site has open registration
-    if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
-      let site: SiteView = site;
-      if !site.open_registration {
+    if let Ok(site_view) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
+      if !site_view.site.open_registration {
         return Err(APIError::err("registration_closed").into());
       }
     }
@@ -154,7 +156,7 @@ impl Perform for Register {
 
     // Make sure there are no admins
     let any_admins = blocking(context.pool(), move |conn| {
-      UserView::admins(conn).map(|a| a.is_empty())
+      UserViewSafe::admins(conn).map(|a| a.is_empty())
     })
     .await??;
     if data.admin && !any_admins {
@@ -491,24 +493,41 @@ impl Perform for GetUserDetails {
     };
 
     let user_id = user.map(|u| u.id);
-    let user_fun = move |conn: &'_ _| {
-      match user_id {
-        // if there's a logged in user and it's the same id as the user whose details are being
-        // requested we need to use get_user_dangerous so it returns their email or other sensitive
-        // data hidden when viewing users other than yourself
-        Some(auth_user_id) => {
-          if user_details_id == auth_user_id {
-            UserView::get_user_dangerous(conn, auth_user_id)
-          } else {
-            UserView::get_user_secure(conn, user_details_id)
-          }
-        }
-        None => UserView::get_user_secure(conn, user_details_id),
+
+    let (user_view, user_dangerous) = if let Some(auth_user_id) = user_id {
+      if user_details_id == auth_user_id {
+        (
+          None,
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewDangerous::read(conn, auth_user_id)
+            })
+            .await??,
+          ),
+        )
+      } else {
+        (
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewSafe::read(conn, user_details_id)
+            })
+            .await??,
+          ),
+          None,
+        )
       }
+    } else {
+      (
+        Some(
+          blocking(context.pool(), move |conn| {
+            UserViewSafe::read(conn, user_details_id)
+          })
+          .await??,
+        ),
+        None,
+      )
     };
 
-    let user_view = blocking(context.pool(), user_fun).await??;
-
     let page = data.page;
     let limit = data.limit;
     let saved_only = data.saved_only;
@@ -556,7 +575,9 @@ impl Perform for GetUserDetails {
 
     // Return the jwt
     Ok(GetUserDetailsResponse {
+      // TODO need to figure out dangerous user view here
       user: user_view,
+      user_dangerous,
       follows,
       moderates,
       comments,
@@ -601,10 +622,10 @@ impl Perform for AddAdmin {
     })
     .await??;
 
-    let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+    let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
     let creator_index = admins
       .iter()
-      .position(|r| r.id == site_creator_id)
+      .position(|r| r.user.id == site_creator_id)
       .context(location_info!())?;
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
@@ -682,7 +703,7 @@ impl Perform for BanUser {
 
     let user_id = data.user_id;
     let user_view = blocking(context.pool(), move |conn| {
-      UserView::get_user_secure(conn, user_id)
+      UserViewSafe::read(conn, user_id)
     })
     .await??;
 
index b1866283b64a538558f51ab83deb08479378740f..16b0c4e3cfb0fecdb525bbe645d9d2444d08eba4 100644 (file)
@@ -4,7 +4,7 @@ use activitystreams::{
   base::{AnyBase, ExtendsExt},
 };
 use anyhow::Context;
-use lemmy_db::{community::Community, community_view::CommunityView, ApubObject};
+use lemmy_db::{community::Community, views::community_view::CommunityView, ApubObject};
 use lemmy_structs::{blocking, community::CommunityResponse};
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
@@ -21,13 +21,13 @@ pub(crate) async fn receive_delete_community(
 
   let community_id = deleted_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -64,13 +64,13 @@ pub(crate) async fn receive_remove_community(
 
   let community_id = removed_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -100,13 +100,13 @@ pub(crate) async fn receive_undo_delete_community(
 
   let community_id = deleted_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
     response: res,
@@ -146,13 +146,13 @@ pub(crate) async fn receive_undo_remove_community(
 
   let community_id = removed_community.id;
   let res = CommunityResponse {
-    community: blocking(context.pool(), move |conn| {
+    community_view: blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, None)
     })
     .await??,
   };
 
-  let community_id = res.community.id;
+  let community_id = res.community_view.community.id;
 
   context.chat_server().do_send(SendCommunityRoomMessage {
     op: UserOperation::EditCommunity,
index 25ccc520d1eecfe674d7cf34d141b48630d822eb..f05b523799904073650702a1fe7e7029341d43e0 100644 (file)
@@ -169,7 +169,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());
   }
index 775f8c25fa8dd011990add83c61c1a7f0f59a634..b1a2352d3dbffb088931be5b20c33751ce9144fa 100644 (file)
@@ -23,7 +23,11 @@ use activitystreams::{
 };
 use anyhow::Context;
 use itertools::Itertools;
-use lemmy_db::{community::Community, community_view::CommunityFollowerView, DbPool};
+use lemmy_db::{
+  community::Community,
+  views::community_follower_view::CommunityFollowerView,
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -179,9 +183,9 @@ impl ActorType for Community {
     .await??;
     let inboxes = inboxes
       .into_iter()
-      .filter(|i| !i.user_local)
+      .filter(|i| !i.follower.local)
       .map(|u| -> Result<Url, LemmyError> {
-        let url = Url::parse(&u.user_actor_id)?;
+        let url = Url::parse(&u.follower.actor_id)?;
         let domain = url.domain().context(location_info!())?;
         let port = if let Some(port) = url.port() {
           format!(":{}", port)
index ad977f5b1b3a371ed9fb1d6aa6bffcf3eead9360..7556580e60d09b5a19522f619802d900e8e71ec2 100644 (file)
@@ -16,12 +16,11 @@ use lemmy_db::{
   comment::Comment,
   comment_view::CommentView,
   community::{Community, CommunityModerator, CommunityModeratorForm},
-  community_view::CommunityView,
   naive_now,
   post::Post,
   post_view::PostView,
   user::User_,
-  user_view::UserView,
+  views::{community_view::CommunityView, user_view::UserViewSafe},
   ApubObject,
   Joinable,
   SearchType,
@@ -161,7 +160,7 @@ pub async fn search_by_apub_id(
 
       response.users = vec![
         blocking(context.pool(), move |conn| {
-          UserView::get_user_secure(conn, user.id)
+          UserViewSafe::read(conn, user.id)
         })
         .await??,
       ];
index 0a90571ff0287bfbb00b8eceb5cd2e60875b2f15..d2a18ee0b3c2a070e9e85cdbb84bcb4baf461e73 100644 (file)
@@ -9,7 +9,11 @@ use activitystreams::{
   collection::{CollectionExt, OrderedCollection, UnorderedCollection},
 };
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::{community::Community, community_view::CommunityFollowerView, post::Post};
+use lemmy_db::{
+  community::Community,
+  post::Post,
+  views::community_follower_view::CommunityFollowerView,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 4bdad2fadcc433dd273459be7b63e69cfc404e7f..1c7b32e908e6c7aa2026afbf4d887b9816287e01 100644 (file)
@@ -28,8 +28,8 @@ use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::{anyhow, Context};
 use lemmy_db::{
   community::{Community, CommunityFollower, CommunityFollowerForm},
-  community_view::CommunityUserBanView,
   user::User_,
+  views::community_user_ban_view::CommunityUserBanView,
   ApubObject,
   DbPool,
   Followable,
@@ -82,7 +82,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());
   }
index e04fdd0ffd032e3ffa429e6392b101a7ad3587a7..d36a34c9b0f718ba4a8584d863880da9aefa47c3 100644 (file)
@@ -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 {
index 826038bf09ccbd37ee5f406dd1c0acb20acd225c..0108622957136f711b9854d6c8f6f80a5f2bea23 100644 (file)
@@ -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.
index 81b9f185378266af85c5fa0c6f4b1027a4c0af28..28f2de60f557953d05c4a71b21b3be28e907e2f1 100644 (file)
@@ -102,7 +102,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());
@@ -173,7 +173,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(());
index 594a5b5ee03956a1a574be545e8c8f1e88f89c12..8cc5b9eb5cb231574d4e1ca40d13d5e329c8aaeb 100644 (file)
@@ -24,8 +24,8 @@ use activitystreams_ext::Ext2;
 use anyhow::Context;
 use lemmy_db::{
   community::{Community, CommunityForm},
-  community_view::CommunityModeratorView,
   naive_now,
+  views::community_moderator_view::CommunityModeratorView,
   DbPool,
 };
 use lemmy_structs::blocking;
@@ -51,7 +51,10 @@ impl ToApub for Community {
       CommunityModeratorView::for_community(&conn, id)
     })
     .await??;
-    let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
+    let moderators: Vec<String> = moderators
+      .into_iter()
+      .map(|m| m.moderator.actor_id)
+      .collect();
 
     let mut group = ApObject::new(Group::new());
     group
diff --git a/lemmy_db/src/aggregates/community_aggregates.rs b/lemmy_db/src/aggregates/community_aggregates.rs
new file mode 100644 (file)
index 0000000..9a8ea36
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..9f38f2e
--- /dev/null
@@ -0,0 +1,3 @@
+pub mod community_aggregates;
+pub mod site_aggregates;
+pub mod user_aggregates;
diff --git a/lemmy_db/src/aggregates/site_aggregates.rs b/lemmy_db/src/aggregates/site_aggregates.rs
new file mode 100644 (file)
index 0000000..fdd4d1c
--- /dev/null
@@ -0,0 +1,162 @@
+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)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::site_aggregates::SiteAggregates,
+    comment::{Comment, CommentForm},
+    community::{Community, CommunityForm},
+    post::{Post, PostForm},
+    tests::establish_unpooled_connection,
+    user::{UserForm, User_},
+    Crud,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_site_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_site_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let site_aggregates_before_delete = SiteAggregates::read(&conn).unwrap();
+
+    assert_eq!(1, site_aggregates_before_delete.users);
+    assert_eq!(1, site_aggregates_before_delete.communities);
+    assert_eq!(1, site_aggregates_before_delete.posts);
+    assert_eq!(2, site_aggregates_before_delete.comments);
+
+    // This shouuld delete all the associated rows, and fire triggers
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+
+    let site_aggregates_after_delete = SiteAggregates::read(&conn).unwrap();
+    assert_eq!(0, site_aggregates_after_delete.users);
+    assert_eq!(0, site_aggregates_after_delete.communities);
+    assert_eq!(0, site_aggregates_after_delete.posts);
+    assert_eq!(0, site_aggregates_after_delete.comments);
+  }
+}
diff --git a/lemmy_db/src/aggregates/user_aggregates.rs b/lemmy_db/src/aggregates/user_aggregates.rs
new file mode 100644 (file)
index 0000000..622bce1
--- /dev/null
@@ -0,0 +1,231 @@
+use crate::schema::user_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "user_aggregates"]
+pub struct UserAggregates {
+  pub id: i32,
+  pub user_id: i32,
+  pub post_count: i64,
+  pub post_score: i64,
+  pub comment_count: i64,
+  pub comment_score: i64,
+}
+
+impl UserAggregates {
+  pub fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
+    user_aggregates::table
+      .filter(user_aggregates::user_id.eq(user_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::user_aggregates::UserAggregates,
+    comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
+    community::{Community, CommunityForm},
+    post::{Post, PostForm, PostLike, PostLikeForm},
+    tests::establish_unpooled_connection,
+    user::{UserForm, User_},
+    Crud,
+    Likeable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_user_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let inserted_user = User_::create(&conn, &new_user).unwrap();
+
+    let another_user = UserForm {
+      name: "jerry_user_agg".into(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      admin: false,
+      banned: Some(false),
+      published: None,
+      updated: None,
+      show_nsfw: false,
+      theme: "browser".into(),
+      default_sort_type: SortType::Hot as i16,
+      default_listing_type: ListingType::Subscribed as i16,
+      lang: "browser".into(),
+      show_avatars: true,
+      send_notifications_to_email: false,
+      actor_id: None,
+      bio: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+    };
+
+    let another_inserted_user = User_::create(&conn, &another_user).unwrap();
+
+    let new_community = CommunityForm {
+      name: "TIL_site_agg".into(),
+      creator_id: inserted_user.id,
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      nsfw: false,
+      removed: None,
+      deleted: None,
+      updated: None,
+      actor_id: None,
+      local: true,
+      private_key: None,
+      public_key: None,
+      last_refreshed_at: None,
+      published: None,
+      icon: None,
+      banner: None,
+    };
+
+    let inserted_community = Community::create(&conn, &new_community).unwrap();
+
+    let new_post = PostForm {
+      name: "A test post".into(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      nsfw: false,
+      updated: None,
+      embed_title: None,
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: None,
+      local: true,
+      published: None,
+    };
+
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+
+    let post_like = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let _inserted_post_like = PostLike::like(&conn, &post_like).unwrap();
+
+    let comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let comment_like = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      user_id: inserted_user.id,
+      post_id: inserted_post.id,
+      score: 1,
+    };
+
+    let _inserted_comment_like = CommentLike::like(&conn, &comment_like).unwrap();
+
+    let child_comment_form = CommentForm {
+      content: "A test comment".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      removed: None,
+      deleted: None,
+      read: None,
+      parent_id: Some(inserted_comment.id),
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
+
+    let child_comment_like = CommentLikeForm {
+      comment_id: inserted_child_comment.id,
+      user_id: another_inserted_user.id,
+      post_id: inserted_post.id,
+      score: 1,
+    };
+
+    let _inserted_child_comment_like = CommentLike::like(&conn, &child_comment_like).unwrap();
+
+    let user_aggregates_before_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+
+    assert_eq!(1, user_aggregates_before_delete.post_count);
+    assert_eq!(1, user_aggregates_before_delete.post_score);
+    assert_eq!(2, user_aggregates_before_delete.comment_count);
+    assert_eq!(2, user_aggregates_before_delete.comment_score);
+
+    // Remove a post like
+    PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let after_post_like_remove = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_post_like_remove.post_score);
+
+    // Remove a parent comment (the scores should also be removed)
+    Comment::delete(&conn, inserted_comment.id).unwrap();
+    let after_parent_comment_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_parent_comment_delete.comment_count);
+    assert_eq!(0, after_parent_comment_delete.comment_score);
+
+    // This should delete all the associated rows, and fire triggers
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+
+    // Should be none found
+    let after_delete = UserAggregates::read(&conn, inserted_user.id);
+    assert!(after_delete.is_err());
+  }
+}
index 36beb9ff638b1a2070d9af70ecb7f4a2c2c6c6d3..af2e72265c22e37b21886c864c43d8fd0fa0a9a2 100644 (file)
@@ -5,7 +5,7 @@ use crate::{
 use diesel::{dsl::*, result::Error, *};
 use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Clone)]
 #[table_name = "category"]
 pub struct Category {
   pub id: i32,
index f54ddd10f203def7fc3820ec4887a1196a379edb..fb327a308dfbe8d63eee316648fa779141f0ddd8 100644 (file)
@@ -190,7 +190,7 @@ pub struct CommentLike {
   pub id: i32,
   pub user_id: i32,
   pub comment_id: i32,
-  pub post_id: i32,
+  pub post_id: i32, // TODO this is redundant
   pub score: i16,
   pub published: chrono::NaiveDateTime,
 }
@@ -200,7 +200,7 @@ pub struct CommentLike {
 pub struct CommentLikeForm {
   pub user_id: i32,
   pub comment_id: i32,
-  pub post_id: i32,
+  pub post_id: i32, // TODO this is redundant
   pub score: i16,
 }
 
index be40da349246692b0e56714294e2fb9fa7e6e361..eda643ad5ca77c5a58a3162c654610a7b51c1da6 100644 (file)
@@ -8,8 +8,9 @@ use crate::{
   Joinable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "community"]
 pub struct Community {
   pub id: i32,
@@ -32,6 +33,71 @@ pub struct Community {
   pub banner: Option<String>,
 }
 
+/// A safe representation of community, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "community"]
+pub struct CommunitySafe {
+  pub id: i32,
+  pub name: String,
+  pub title: String,
+  pub description: Option<String>,
+  pub category_id: i32,
+  pub creator_id: i32,
+  pub removed: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub deleted: bool,
+  pub nsfw: bool,
+  pub actor_id: String,
+  pub local: bool,
+  pub icon: Option<String>,
+  pub banner: Option<String>,
+}
+
+mod safe_type {
+  use crate::{community::Community, schema::community::columns::*, ToSafe};
+  type Columns = (
+    id,
+    name,
+    title,
+    description,
+    category_id,
+    creator_id,
+    removed,
+    published,
+    updated,
+    deleted,
+    nsfw,
+    actor_id,
+    local,
+    icon,
+    banner,
+  );
+
+  impl ToSafe for Community {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        title,
+        description,
+        category_id,
+        creator_id,
+        removed,
+        published,
+        updated,
+        deleted,
+        nsfw,
+        actor_id,
+        local,
+        icon,
+        banner,
+      )
+    }
+  }
+}
+
 #[derive(Insertable, AsChangeset, Debug)]
 #[table_name = "community"]
 pub struct CommunityForm {
@@ -157,14 +223,14 @@ impl Community {
   }
 
   fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
-    use crate::{community_view::CommunityModeratorView, user_view::UserView};
+    use crate::views::{community_moderator_view::CommunityModeratorView, user_view::UserViewSafe};
     let mut mods_and_admins: Vec<i32> = Vec::new();
     mods_and_admins.append(
       &mut CommunityModeratorView::for_community(conn, community_id)
-        .map(|v| v.into_iter().map(|m| m.user_id).collect())?,
+        .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?,
     );
     mods_and_admins
-      .append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?);
+      .append(&mut UserViewSafe::admins(conn).map(|v| v.into_iter().map(|a| a.user.id).collect())?);
     Ok(mods_and_admins)
   }
 
diff --git a/lemmy_db/src/community_view.rs b/lemmy_db/src/community_view.rs
deleted file mode 100644 (file)
index a635550..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-use super::community_view::community_fast_view::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
-use diesel::{pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-table! {
-  community_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    title -> Varchar,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    description -> Nullable<Text>,
-    category_id -> Int4,
-    creator_id -> Int4,
-    removed -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    actor_id -> Text,
-    local -> Bool,
-    last_refreshed_at -> Timestamp,
-    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>,
-  }
-}
-
-table! {
-  community_fast_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    title -> Varchar,
-    icon -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    description -> Nullable<Text>,
-    category_id -> Int4,
-    creator_id -> Int4,
-    removed -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    actor_id -> Text,
-    local -> Bool,
-    last_refreshed_at -> Timestamp,
-    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>,
-  }
-}
-
-table! {
-  community_moderator_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-table! {
-  community_follower_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-table! {
-  community_user_ban_view (id) {
-    id -> Int4,
-    community_id -> Int4,
-    user_id -> Int4,
-    published -> Timestamp,
-    user_actor_id -> Text,
-    user_local -> Bool,
-    user_name -> Varchar,
-    user_preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "community_fast_view"]
-pub struct CommunityView {
-  pub id: i32,
-  pub name: String,
-  pub title: String,
-  pub icon: Option<String>,
-  pub banner: Option<String>,
-  pub description: Option<String>,
-  pub category_id: i32,
-  pub creator_id: i32,
-  pub removed: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub nsfw: bool,
-  pub actor_id: String,
-  pub local: bool,
-  pub last_refreshed_at: chrono::NaiveDateTime,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub category_name: String,
-  pub number_of_subscribers: i64,
-  pub number_of_posts: i64,
-  pub number_of_comments: i64,
-  pub hot_rank: i32,
-  pub user_id: Option<i32>,
-  pub subscribed: Option<bool>,
-}
-
-pub struct CommunityQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  sort: &'a SortType,
-  from_user_id: Option<i32>,
-  show_nsfw: bool,
-  search_term: Option<String>,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> CommunityQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let query = community_fast_view.into_boxed();
-
-    CommunityQueryBuilder {
-      conn,
-      query,
-      sort: &SortType::Hot,
-      from_user_id: None,
-      show_nsfw: true,
-      search_term: None,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_user<T: MaybeOptional<i32>>(mut self, from_user_id: T) -> Self {
-    self.from_user_id = from_user_id.get_optional();
-    self
-  }
-
-  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
-    self.show_nsfw = show_nsfw;
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    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<CommunityView>, Error> {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    if let Some(search_term) = self.search_term {
-      let searcher = fuzzy_search(&search_term);
-      query = query
-        .filter(name.ilike(searcher.to_owned()))
-        .or_filter(title.ilike(searcher.to_owned()))
-        .or_filter(description.ilike(searcher));
-    };
-
-    // The view lets you pass a null user_id, if you're not logged in
-    match self.sort {
-      SortType::New => query = query.order_by(published.desc()).filter(user_id.is_null()),
-      SortType::TopAll => match self.from_user_id {
-        Some(from_user_id) => {
-          query = query
-            .filter(user_id.eq(from_user_id))
-            .order_by((subscribed.asc(), number_of_subscribers.desc()))
-        }
-        None => {
-          query = query
-            .order_by(number_of_subscribers.desc())
-            .filter(user_id.is_null())
-        }
-      },
-      // Covers all other sorts, including hot
-      _ => {
-        query = query
-          .order_by(hot_rank.desc())
-          .then_order_by(number_of_subscribers.desc())
-          .filter(user_id.is_null())
-      }
-    };
-
-    if !self.show_nsfw {
-      query = query.filter(nsfw.eq(false));
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .filter(removed.eq(false))
-      .filter(deleted.eq(false))
-      .load::<CommunityView>(self.conn)
-  }
-}
-
-impl CommunityView {
-  pub fn read(
-    conn: &PgConnection,
-    from_community_id: i32,
-    from_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::community_view::community_fast_view::dsl::*;
-
-    let mut query = community_fast_view.into_boxed();
-
-    query = query.filter(id.eq(from_community_id));
-
-    // The view lets you pass a null user_id, if you're not logged in
-    if let Some(from_user_id) = from_user_id {
-      query = query.filter(user_id.eq(from_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    };
-
-    query.first::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_moderator_view"]
-pub struct CommunityModeratorView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityModeratorView {
-  pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_moderator_view::dsl::*;
-    community_moderator_view
-      .filter(community_id.eq(for_community_id))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-
-  pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_moderator_view::dsl::*;
-    community_moderator_view
-      .filter(user_id.eq(for_user_id))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_follower_view"]
-pub struct CommunityFollowerView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityFollowerView {
-  pub fn for_community(conn: &PgConnection, from_community_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_follower_view::dsl::*;
-    community_follower_view
-      .filter(community_id.eq(from_community_id))
-      .load::<Self>(conn)
-  }
-
-  pub fn for_user(conn: &PgConnection, from_user_id: i32) -> Result<Vec<Self>, Error> {
-    use super::community_view::community_follower_view::dsl::*;
-    community_follower_view
-      .filter(user_id.eq(from_user_id))
-      .load::<Self>(conn)
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "community_user_ban_view"]
-pub struct CommunityUserBanView {
-  pub id: i32,
-  pub community_id: i32,
-  pub user_id: i32,
-  pub published: chrono::NaiveDateTime,
-  pub user_actor_id: String,
-  pub user_local: bool,
-  pub user_name: String,
-  pub user_preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-}
-
-impl CommunityUserBanView {
-  pub fn get(
-    conn: &PgConnection,
-    from_user_id: i32,
-    from_community_id: i32,
-  ) -> Result<Self, Error> {
-    use super::community_view::community_user_ban_view::dsl::*;
-    community_user_ban_view
-      .filter(user_id.eq(from_user_id))
-      .filter(community_id.eq(from_community_id))
-      .first::<Self>(conn)
-  }
-}
index 0ca4826a19486ebbb11cd3b9f5be4dfbab632f25..0cf1cd61c6f3dfadd3847287b8f30446f6cb4771 100644 (file)
@@ -12,12 +12,12 @@ use serde::{Deserialize, Serialize};
 use std::{env, env::VarError};
 
 pub mod activity;
+pub mod aggregates;
 pub mod category;
 pub mod comment;
 pub mod comment_report;
 pub mod comment_view;
 pub mod community;
-pub mod community_view;
 pub mod moderator;
 pub mod moderator_views;
 pub mod password_reset_request;
@@ -28,11 +28,10 @@ pub mod private_message;
 pub mod private_message_view;
 pub mod schema;
 pub mod site;
-pub mod site_view;
 pub mod user;
 pub mod user_mention;
 pub mod user_mention_view;
-pub mod user_view;
+pub mod views;
 
 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
 
@@ -149,6 +148,11 @@ impl<T> MaybeOptional<T> for Option<T> {
   }
 }
 
+pub(crate) trait ToSafe {
+  type SafeColumns;
+  fn safe_columns_tuple() -> Self::SafeColumns;
+}
+
 pub fn get_database_url_from_env() -> Result<String, VarError> {
   env::var("LEMMY_DATABASE_URL")
 }
@@ -222,6 +226,14 @@ lazy_static! {
     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
 }
 
+pub(crate) mod functions {
+  use diesel::sql_types::*;
+
+  sql_function! {
+    fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
+  }
+}
+
 #[cfg(test)]
 mod tests {
   use super::fuzzy_search;
index 49bbc46fb1c74fa29af44032c3d603ca43efc73d..e6dd6d4bd2f6b4c8dd452e51bff7c7549915573d 100644 (file)
@@ -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,
@@ -440,6 +450,16 @@ table! {
     }
 }
 
+table! {
+    site_aggregates (id) {
+        id -> Int4,
+        users -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+        communities -> Int8,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -471,6 +491,17 @@ table! {
     }
 }
 
+table! {
+    user_aggregates (id) {
+        id -> Int4,
+        user_id -> Int4,
+        post_count -> Int8,
+        post_score -> Int8,
+        comment_count -> Int8,
+        comment_score -> Int8,
+    }
+}
+
 table! {
     user_ban (id) {
         id -> Int4,
@@ -523,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));
@@ -552,6 +584,7 @@ joinable!(post_report -> post (post_id));
 joinable!(post_saved -> post (post_id));
 joinable!(post_saved -> user_ (user_id));
 joinable!(site -> user_ (creator_id));
+joinable!(user_aggregates -> user_ (user_id));
 joinable!(user_ban -> user_ (user_id));
 joinable!(user_mention -> comment (comment_id));
 joinable!(user_mention -> user_ (recipient_id));
@@ -565,6 +598,7 @@ allow_tables_to_appear_in_same_query!(
   comment_report,
   comment_saved,
   community,
+  community_aggregates,
   community_aggregates_fast,
   community_follower,
   community_moderator,
@@ -587,7 +621,9 @@ allow_tables_to_appear_in_same_query!(
   post_saved,
   private_message,
   site,
+  site_aggregates,
   user_,
+  user_aggregates,
   user_ban,
   user_fast,
   user_mention,
index 5e68fead8afbb1078c791678e747fe8fa2d31095..2f3fbcdff15859930d23a1676b3397295fa5e65a 100644 (file)
@@ -1,7 +1,8 @@
 use crate::{naive_now, schema::site, Crud};
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Queryable, Identifiable, PartialEq, Debug, Clone, Serialize)]
 #[table_name = "site"]
 pub struct Site {
   pub id: i32,
diff --git a/lemmy_db/src/site_view.rs b/lemmy_db/src/site_view.rs
deleted file mode 100644 (file)
index fd15ac7..0000000
+++ /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)
-  }
-}
index d8e833e6e5bdb2540e7d5d3777c821446edaa10e..41d3ed18b74a01d4f10a2a1013c917848695ed2c 100644 (file)
@@ -41,6 +41,68 @@ pub struct User_ {
   pub deleted: bool,
 }
 
+/// A safe representation of user, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_"]
+pub struct UserSafe {
+  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 {
+  use crate::{schema::user_::columns::*, user::User_, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for User_ {
+    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 {
@@ -274,6 +336,7 @@ mod tests {
       private_key: None,
       public_key: None,
       last_refreshed_at: inserted_user.published,
+      deleted: false,
     };
 
     let read_user = User_::read(&conn, inserted_user.id).unwrap();
diff --git a/lemmy_db/src/user_view.rs b/lemmy_db/src/user_view.rs
deleted file mode 100644 (file)
index bf85280..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-use super::user_view::user_fast::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-table! {
-  user_view (id) {
-    id -> Int4,
-    actor_id -> Text,
-    name -> Varchar,
-    preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    email -> Nullable<Text>,
-    matrix_user_id -> Nullable<Text>,
-    bio -> Nullable<Text>,
-    local -> Bool,
-    admin -> Bool,
-    banned -> Bool,
-    show_avatars -> Bool,
-    send_notifications_to_email -> Bool,
-    published -> Timestamp,
-    number_of_posts -> BigInt,
-    post_score -> BigInt,
-    number_of_comments -> BigInt,
-    comment_score -> BigInt,
-  }
-}
-
-table! {
-  user_fast (id) {
-    id -> Int4,
-    actor_id -> Text,
-    name -> Varchar,
-    preferred_username -> Nullable<Varchar>,
-    avatar -> Nullable<Text>,
-    banner -> Nullable<Text>,
-    email -> Nullable<Text>,
-    matrix_user_id -> Nullable<Text>,
-    bio -> Nullable<Text>,
-    local -> Bool,
-    admin -> Bool,
-    banned -> Bool,
-    show_avatars -> Bool,
-    send_notifications_to_email -> Bool,
-    published -> Timestamp,
-    number_of_posts -> BigInt,
-    post_score -> BigInt,
-    number_of_comments -> BigInt,
-    comment_score -> BigInt,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "user_fast"]
-pub struct UserView {
-  pub id: i32,
-  pub actor_id: String,
-  pub name: String,
-  pub preferred_username: Option<String>,
-  pub avatar: Option<String>,
-  pub banner: Option<String>,
-  pub email: Option<String>, // TODO this shouldn't be in this view
-  pub matrix_user_id: Option<String>,
-  pub bio: Option<String>,
-  pub local: bool,
-  pub admin: bool,
-  pub banned: bool,
-  pub show_avatars: bool, // TODO this is a setting, probably doesn't need to be here
-  pub send_notifications_to_email: bool, // TODO also never used
-  pub published: chrono::NaiveDateTime,
-  pub number_of_posts: i64,
-  pub post_score: i64,
-  pub number_of_comments: i64,
-  pub comment_score: i64,
-}
-
-pub struct UserQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  sort: &'a SortType,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> UserQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::user_view::user_fast::dsl::*;
-
-    let query = user_fast.into_boxed();
-
-    UserQueryBuilder {
-      conn,
-      query,
-      sort: &SortType::Hot,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    use super::user_view::user_fast::dsl::*;
-    if let Some(search_term) = search_term.get_optional() {
-      self.query = self.query.filter(name.ilike(fuzzy_search(&search_term)));
-    }
-    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<UserView>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-
-    let mut query = self.query;
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(comment_score.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(comment_score.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(comment_score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(comment_score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(comment_score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(comment_score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(comment_score.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query = query.limit(limit).offset(offset);
-
-    // The select is necessary here to not get back emails
-    query = query.select((
-      id,
-      actor_id,
-      name,
-      preferred_username,
-      avatar,
-      banner,
-      "".into_sql::<Nullable<Text>>(),
-      matrix_user_id,
-      bio,
-      local,
-      admin,
-      banned,
-      show_avatars,
-      send_notifications_to_email,
-      published,
-      number_of_posts,
-      post_score,
-      number_of_comments,
-      comment_score,
-    ));
-    query.load::<UserView>(self.conn)
-  }
-}
-
-impl UserView {
-  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      // The select is necessary here to not get back emails
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .filter(admin.eq(true))
-      .order_by(published)
-      .load::<Self>(conn)
-  }
-
-  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .filter(banned.eq(true))
-      .load::<Self>(conn)
-  }
-
-  // WARNING!!! this method WILL return sensitive user information and should only be called
-  // if the user requesting these details is also the authenticated user.
-  // please use get_user_secure to obtain user rows in most cases.
-  pub fn get_user_dangerous(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
-    use super::user_view::user_fast::dsl::*;
-    user_fast.find(user_id).first::<Self>(conn)
-  }
-
-  pub fn get_user_secure(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
-    use super::user_view::user_fast::dsl::*;
-    use diesel::sql_types::{Nullable, Text};
-    user_fast
-      .select((
-        id,
-        actor_id,
-        name,
-        preferred_username,
-        avatar,
-        banner,
-        "".into_sql::<Nullable<Text>>(),
-        matrix_user_id,
-        bio,
-        local,
-        admin,
-        banned,
-        show_avatars,
-        send_notifications_to_email,
-        published,
-        number_of_posts,
-        post_score,
-        number_of_comments,
-        comment_score,
-      ))
-      .find(user_id)
-      .first::<Self>(conn)
-  }
-}
diff --git a/lemmy_db/src/views/community_follower_view.rs b/lemmy_db/src/views/community_follower_view.rs
new file mode 100644 (file)
index 0000000..faded4d
--- /dev/null
@@ -0,0 +1,50 @@
+use crate::{
+  community::{Community, CommunitySafe},
+  schema::{community, community_follower, user_},
+  user::{UserSafe, User_},
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityFollowerView {
+  pub community: CommunitySafe,
+  pub follower: UserSafe,
+}
+
+impl CommunityFollowerView {
+  pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_follower::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_follower::community_id.eq(for_community_id))
+      .order_by(community_follower::published)
+      .load::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_follower::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_follower::user_id.eq(for_user_id))
+      .order_by(community_follower::published)
+      .load::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(to_vec(res))
+  }
+}
+
+fn to_vec(users: Vec<(CommunitySafe, UserSafe)>) -> Vec<CommunityFollowerView> {
+  users
+    .iter()
+    .map(|a| CommunityFollowerView {
+      community: a.0.to_owned(),
+      follower: a.1.to_owned(),
+    })
+    .collect::<Vec<CommunityFollowerView>>()
+}
diff --git a/lemmy_db/src/views/community_moderator_view.rs b/lemmy_db/src/views/community_moderator_view.rs
new file mode 100644 (file)
index 0000000..5cdcbf8
--- /dev/null
@@ -0,0 +1,50 @@
+use crate::{
+  community::{Community, CommunitySafe},
+  schema::{community, community_moderator, user_},
+  user::{UserSafe, User_},
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityModeratorView {
+  pub community: CommunitySafe,
+  pub moderator: UserSafe,
+}
+
+impl CommunityModeratorView {
+  pub fn for_community(conn: &PgConnection, for_community_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_moderator::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_moderator::community_id.eq(for_community_id))
+      .order_by(community_moderator::published)
+      .load::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, for_user_id: i32) -> Result<Vec<Self>, Error> {
+    let res = community_moderator::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_moderator::user_id.eq(for_user_id))
+      .order_by(community_moderator::published)
+      .load::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(to_vec(res))
+  }
+}
+
+fn to_vec(users: Vec<(CommunitySafe, UserSafe)>) -> Vec<CommunityModeratorView> {
+  users
+    .iter()
+    .map(|a| CommunityModeratorView {
+      community: a.0.to_owned(),
+      moderator: a.1.to_owned(),
+    })
+    .collect::<Vec<CommunityModeratorView>>()
+}
diff --git a/lemmy_db/src/views/community_user_ban_view.rs b/lemmy_db/src/views/community_user_ban_view.rs
new file mode 100644 (file)
index 0000000..faaae0f
--- /dev/null
@@ -0,0 +1,33 @@
+use crate::{
+  community::{Community, CommunitySafe},
+  schema::{community, community_user_ban, user_},
+  user::{UserSafe, User_},
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityUserBanView {
+  pub community: CommunitySafe,
+  pub user: UserSafe,
+}
+
+impl CommunityUserBanView {
+  pub fn get(
+    conn: &PgConnection,
+    from_user_id: i32,
+    from_community_id: i32,
+  ) -> Result<Self, Error> {
+    let (community, user) = community_user_ban::table
+      .inner_join(community::table)
+      .inner_join(user_::table)
+      .select((Community::safe_columns_tuple(), User_::safe_columns_tuple()))
+      .filter(community_user_ban::community_id.eq(from_community_id))
+      .filter(community_user_ban::user_id.eq(from_user_id))
+      .order_by(community_user_ban::published)
+      .first::<(CommunitySafe, UserSafe)>(conn)?;
+
+    Ok(CommunityUserBanView { community, user })
+  }
+}
diff --git a/lemmy_db/src/views/community_view.rs b/lemmy_db/src/views/community_view.rs
new file mode 100644 (file)
index 0000000..cbb90a4
--- /dev/null
@@ -0,0 +1,304 @@
+use crate::{
+  aggregates::community_aggregates::CommunityAggregates,
+  category::Category,
+  community::{Community, CommunityFollower, CommunitySafe},
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{category, community, community_aggregates, community_follower, user_},
+  user::{UserSafe, User_},
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityView {
+  pub community: CommunitySafe,
+  pub creator: UserSafe,
+  pub category: Category,
+  pub subscribed: bool,
+  pub counts: CommunityAggregates,
+}
+
+impl CommunityView {
+  pub fn read(
+    conn: &PgConnection,
+    community_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 (community, creator, category, counts, subscribed) = community::table
+      .find(community_id)
+      .inner_join(user_::table)
+      .inner_join(category::table)
+      .inner_join(community_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          community::id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        category::all_columns,
+        community_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+      ))
+      .first::<(
+        CommunitySafe,
+        UserSafe,
+        Category,
+        CommunityAggregates,
+        Option<CommunityFollower>,
+      )>(conn)?;
+
+    Ok(CommunityView {
+      community,
+      creator,
+      category,
+      subscribed: subscribed.is_some(),
+      counts,
+    })
+  }
+}
+
+mod join_types {
+  use crate::schema::{category, community, community_aggregates, community_follower, user_};
+  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 BoxedCommunityJoin<'a> = BoxedSelectStatement<
+    'a,
+    (
+      (
+        Integer,
+        Text,
+        Text,
+        Nullable<Text>,
+        Integer,
+        Integer,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Bool,
+        Text,
+        Bool,
+        Nullable<Text>,
+        Nullable<Text>,
+      ),
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Nullable<Text>,
+        Text,
+        Nullable<Text>,
+        Bool,
+        Nullable<Text>,
+        Bool,
+      ),
+      (Integer, Text),
+      (Integer, Integer, BigInt, BigInt, BigInt),
+      Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
+    ),
+    JoinOn<
+      Join<
+        JoinOn<
+          Join<
+            JoinOn<
+              Join<
+                JoinOn<
+                  Join<community::table, user_::table, Inner>,
+                  diesel::expression::operators::Eq<
+                    diesel::expression::nullable::Nullable<community::columns::creator_id>,
+                    diesel::expression::nullable::Nullable<user_::columns::id>,
+                  >,
+                >,
+                category::table,
+                Inner,
+              >,
+              diesel::expression::operators::Eq<
+                diesel::expression::nullable::Nullable<community::columns::category_id>,
+                diesel::expression::nullable::Nullable<category::columns::id>,
+              >,
+            >,
+            community_aggregates::table,
+            Inner,
+          >,
+          diesel::expression::operators::Eq<
+            diesel::expression::nullable::Nullable<community_aggregates::columns::community_id>,
+            diesel::expression::nullable::Nullable<community::columns::id>,
+          >,
+        >,
+        community_follower::table,
+        LeftOuter,
+      >,
+      diesel::expression::operators::And<
+        diesel::expression::operators::Eq<
+          community::columns::id,
+          community_follower::columns::community_id,
+        >,
+        diesel::expression::operators::Eq<
+          community_follower::columns::user_id,
+          diesel::expression::bound::Bound<diesel::sql_types::Integer, i32>,
+        >,
+      >,
+    >,
+    Pg,
+  >;
+}
+
+pub struct CommunityQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  query: join_types::BoxedCommunityJoin<'a>,
+  sort: &'a SortType,
+  show_nsfw: bool,
+  search_term: Option<String>,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> CommunityQueryBuilder<'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 = community::table
+      .inner_join(user_::table)
+      .inner_join(category::table)
+      .inner_join(community_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          community::id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        Community::safe_columns_tuple(),
+        User_::safe_columns_tuple(),
+        category::all_columns,
+        community_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+      ))
+      .into_boxed();
+
+    CommunityQueryBuilder {
+      conn,
+      query,
+      sort: &SortType::Hot,
+      show_nsfw: true,
+      search_term: None,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
+    self.show_nsfw = show_nsfw;
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    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<CommunityView>, Error> {
+    let mut query = self.query;
+
+    if let Some(search_term) = self.search_term {
+      let searcher = fuzzy_search(&search_term);
+      query = query
+        .filter(community::name.ilike(searcher.to_owned()))
+        .or_filter(community::title.ilike(searcher.to_owned()))
+        .or_filter(community::description.ilike(searcher));
+    };
+
+    match self.sort {
+      SortType::New => query = query.order_by(community::published.desc()),
+      SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
+      // Covers all other sorts, including hot
+      _ => {
+        query = query
+          // TODO do custom sql function for hot_rank, make sure this works
+          .order_by(hot_rank(community_aggregates::subscribers, community::published).desc())
+          .then_order_by(community_aggregates::subscribers.desc())
+      }
+    };
+
+    if !self.show_nsfw {
+      query = query.filter(community::nsfw.eq(false));
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .filter(community::removed.eq(false))
+      .filter(community::deleted.eq(false))
+      .load::<(
+        CommunitySafe,
+        UserSafe,
+        Category,
+        CommunityAggregates,
+        Option<CommunityFollower>,
+      )>(self.conn)?;
+
+    Ok(to_vec(res))
+  }
+}
+
+fn to_vec(
+  users: Vec<(
+    CommunitySafe,
+    UserSafe,
+    Category,
+    CommunityAggregates,
+    Option<CommunityFollower>,
+  )>,
+) -> Vec<CommunityView> {
+  users
+    .iter()
+    .map(|a| CommunityView {
+      community: a.0.to_owned(),
+      creator: a.1.to_owned(),
+      category: a.2.to_owned(),
+      counts: a.3.to_owned(),
+      subscribed: a.4.is_some(),
+    })
+    .collect::<Vec<CommunityView>>()
+}
diff --git a/lemmy_db/src/views/mod.rs b/lemmy_db/src/views/mod.rs
new file mode 100644 (file)
index 0000000..0aff8ea
--- /dev/null
@@ -0,0 +1,8 @@
+pub mod community_follower_view;
+pub mod community_moderator_view;
+pub mod community_user_ban_view;
+pub mod community_view;
+pub mod site_view;
+pub mod user_view;
+
+// TODO Every single aggregate trigger is likely broken, you need to test every one of these out
diff --git a/lemmy_db/src/views/site_view.rs b/lemmy_db/src/views/site_view.rs
new file mode 100644 (file)
index 0000000..c00b837
--- /dev/null
@@ -0,0 +1,25 @@
+use crate::{
+  schema::{site, user_},
+  site::Site,
+  user::{UserSafe, User_},
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct SiteView {
+  pub site: Site,
+  pub creator: UserSafe,
+}
+
+impl SiteView {
+  pub fn read(conn: &PgConnection) -> Result<Self, Error> {
+    let (site, creator) = site::table
+      .inner_join(user_::table)
+      .select((site::all_columns, User_::safe_columns_tuple()))
+      .first::<(Site, UserSafe)>(conn)?;
+
+    Ok(SiteView { site, creator })
+  }
+}
diff --git a/lemmy_db/src/views/user_view.rs b/lemmy_db/src/views/user_view.rs
new file mode 100644 (file)
index 0000000..76bc3c3
--- /dev/null
@@ -0,0 +1,203 @@
+use crate::{
+  aggregates::user_aggregates::UserAggregates,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{user_, user_aggregates},
+  user::{UserSafe, User_},
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewSafe {
+  pub user: UserSafe,
+  pub counts: UserAggregates,
+}
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewDangerous {
+  pub user: User_,
+  pub counts: UserAggregates,
+}
+
+impl UserViewDangerous {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let (user, counts) = user_::table
+      .find(id)
+      .inner_join(user_aggregates::table)
+      .first::<(User_, UserAggregates)>(conn)?;
+    Ok(Self { user, counts })
+  }
+}
+
+impl UserViewSafe {
+  pub fn read(conn: &PgConnection, id: i32) -> Result<Self, Error> {
+    let (user, counts) = user_::table
+      .find(id)
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .first::<(UserSafe, UserAggregates)>(conn)?;
+    Ok(Self { user, counts })
+  }
+
+  pub fn admins(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let admins = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .filter(user_::admin.eq(true))
+      .order_by(user_::published)
+      .load::<(UserSafe, UserAggregates)>(conn)?;
+
+    Ok(to_vec(admins))
+  }
+
+  pub fn banned(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+    let banned = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .filter(user_::banned.eq(true))
+      .load::<(UserSafe, UserAggregates)>(conn)?;
+
+    Ok(to_vec(banned))
+  }
+}
+
+mod join_types {
+  use crate::schema::{user_, user_aggregates};
+  use diesel::{
+    pg::Pg,
+    query_builder::BoxedSelectStatement,
+    query_source::joins::{Inner, Join, JoinOn},
+    sql_types::*,
+  };
+
+  /// TODO awful, but necessary because of the boxed join
+  pub(super) type BoxedUserJoin<'a> = BoxedSelectStatement<
+    'a,
+    (
+      // UserSafe column types
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Nullable<Text>,
+        Text,
+        Nullable<Text>,
+        Bool,
+        Nullable<Text>,
+        Bool,
+      ),
+      // UserAggregates column types
+      (Integer, Integer, BigInt, BigInt, BigInt, BigInt),
+    ),
+    JoinOn<
+      Join<user_::table, user_aggregates::table, Inner>,
+      diesel::expression::operators::Eq<
+        diesel::expression::nullable::Nullable<user_aggregates::columns::user_id>,
+        diesel::expression::nullable::Nullable<user_::columns::id>,
+      >,
+    >,
+    Pg,
+  >;
+}
+
+pub struct UserQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  query: join_types::BoxedUserJoin<'a>,
+  sort: &'a SortType,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> UserQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    let query = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .into_boxed();
+
+    UserQueryBuilder {
+      conn,
+      query,
+      sort: &SortType::Hot,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    if let Some(search_term) = search_term.get_optional() {
+      self.query = self
+        .query
+        .filter(user_::name.ilike(fuzzy_search(&search_term)));
+    }
+    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<UserViewSafe>, Error> {
+    let mut query = self.query;
+
+    query = match self.sort {
+      SortType::Hot => query
+        .order_by(user_aggregates::comment_score.desc())
+        .then_order_by(user_::published.desc()),
+      SortType::Active => query
+        .order_by(user_aggregates::comment_score.desc())
+        .then_order_by(user_::published.desc()),
+      SortType::New => query.order_by(user_::published.desc()),
+      SortType::TopAll => query.order_by(user_aggregates::comment_score.desc()),
+      SortType::TopYear => query
+        .filter(user_::published.gt(now - 1.years()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopMonth => query
+        .filter(user_::published.gt(now - 1.months()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopWeek => query
+        .filter(user_::published.gt(now - 1.weeks()))
+        .order_by(user_aggregates::comment_score.desc()),
+      SortType::TopDay => query
+        .filter(user_::published.gt(now - 1.days()))
+        .order_by(user_aggregates::comment_score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+    query = query.limit(limit).offset(offset);
+
+    let res = query.load::<(UserSafe, UserAggregates)>(self.conn)?;
+
+    Ok(to_vec(res))
+  }
+}
+
+fn to_vec(users: Vec<(UserSafe, UserAggregates)>) -> Vec<UserViewSafe> {
+  users
+    .iter()
+    .map(|a| UserViewSafe {
+      user: a.0.to_owned(),
+      counts: a.1.to_owned(),
+    })
+    .collect::<Vec<UserViewSafe>>()
+}
index 3535c05a9fb029a54126dba188b064ee5dc1c884..c107084bb92beb6d967ed88446cd5e2115f8cc84 100644 (file)
@@ -1,6 +1,8 @@
-use lemmy_db::{
-  community_view::{CommunityFollowerView, CommunityModeratorView, CommunityView},
-  user_view::UserView,
+use lemmy_db::views::{
+  community_follower_view::CommunityFollowerView,
+  community_moderator_view::CommunityModeratorView,
+  community_view::CommunityView,
+  user_view::UserViewSafe,
 };
 use serde::{Deserialize, Serialize};
 
@@ -13,7 +15,7 @@ pub struct GetCommunity {
 
 #[derive(Serialize)]
 pub struct GetCommunityResponse {
-  pub community: CommunityView,
+  pub community_view: CommunityView,
   pub moderators: Vec<CommunityModeratorView>,
   pub online: usize,
 }
@@ -32,7 +34,7 @@ pub struct CreateCommunity {
 
 #[derive(Serialize, Clone)]
 pub struct CommunityResponse {
-  pub community: CommunityView,
+  pub community_view: CommunityView,
 }
 
 #[derive(Deserialize, Debug)]
@@ -61,7 +63,7 @@ pub struct BanFromCommunity {
 
 #[derive(Serialize, Clone)]
 pub struct BanFromCommunityResponse {
-  pub user: UserView,
+  pub user_view: UserViewSafe,
   pub banned: bool,
 }
 
index 331c2dca45e944636d101b65697db5920af8176c..8d4be325b5a4b45e5663f5657368d18f1db19942 100644 (file)
@@ -1,8 +1,8 @@
 use lemmy_db::{
   comment_view::CommentView,
-  community_view::{CommunityModeratorView, CommunityView},
   post_report::PostReportView,
   post_view::PostView,
+  views::{community_moderator_view::CommunityModeratorView, community_view::CommunityView},
 };
 use serde::{Deserialize, Serialize};
 
index 3f185928b4b2c1fb61322fb8a74da9a96b03f6e4..9f2efd79de4feabcaa4c7a4691cac999377df110 100644 (file)
@@ -1,12 +1,11 @@
 use lemmy_db::{
+  aggregates::site_aggregates::SiteAggregates,
   category::*,
   comment_view::*,
-  community_view::*,
   moderator_views::*,
   post_view::*,
-  site_view::*,
   user::*,
-  user_view::*,
+  views::{community_view::CommunityView, site_view::SiteView, user_view::UserViewSafe},
 };
 use serde::{Deserialize, Serialize};
 
@@ -36,7 +35,7 @@ pub struct SearchResponse {
   pub comments: Vec<CommentView>,
   pub posts: Vec<PostView>,
   pub communities: Vec<CommunityView>,
-  pub users: Vec<UserView>,
+  pub users: Vec<UserViewSafe>,
 }
 
 #[derive(Deserialize)]
@@ -89,6 +88,7 @@ pub struct GetSite {
   pub auth: Option<String>,
 }
 
+// TODO combine siteresponse and getsiteresponse
 #[derive(Serialize, Clone)]
 pub struct SiteResponse {
   pub site: SiteView,
@@ -96,9 +96,10 @@ pub struct SiteResponse {
 
 #[derive(Serialize)]
 pub struct GetSiteResponse {
-  pub site: Option<SiteView>,
-  pub admins: Vec<UserView>,
-  pub banned: Vec<UserView>,
+  pub site: Option<SiteView>, // Because the site might not be set up yet
+  pub counts: SiteAggregates,
+  pub admins: Vec<UserViewSafe>,
+  pub banned: Vec<UserViewSafe>,
   pub online: usize,
   pub version: String,
   pub my_user: Option<User_>,
index bf4a362860557365dedfe8c8f1ec2967ab1371c7..0a7c6f08bfb1c600e840ef2dccfb5db425c876c1 100644 (file)
@@ -1,10 +1,13 @@
 use lemmy_db::{
   comment_view::{CommentView, ReplyView},
-  community_view::{CommunityFollowerView, CommunityModeratorView},
   post_view::PostView,
   private_message_view::PrivateMessageView,
   user_mention_view::UserMentionView,
-  user_view::UserView,
+  views::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    user_view::{UserViewDangerous, UserViewSafe},
+  },
 };
 use serde::{Deserialize, Serialize};
 
@@ -81,7 +84,8 @@ pub struct GetUserDetails {
 
 #[derive(Serialize)]
 pub struct GetUserDetailsResponse {
-  pub user: UserView,
+  pub user: Option<UserViewSafe>,
+  pub user_dangerous: Option<UserViewDangerous>,
   pub follows: Vec<CommunityFollowerView>,
   pub moderates: Vec<CommunityModeratorView>,
   pub comments: Vec<CommentView>,
@@ -112,7 +116,7 @@ pub struct AddAdmin {
 
 #[derive(Serialize, Clone)]
 pub struct AddAdminResponse {
-  pub admins: Vec<UserView>,
+  pub admins: Vec<UserViewSafe>,
 }
 
 #[derive(Deserialize)]
@@ -127,7 +131,7 @@ pub struct BanUser {
 
 #[derive(Serialize, Clone)]
 pub struct BanUserResponse {
-  pub user: UserView,
+  pub user: UserViewSafe,
   pub banned: bool,
 }
 
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 (file)
index 0000000..4bbee76
--- /dev/null
@@ -0,0 +1,11 @@
+-- Site aggregates
+drop table site_aggregates;
+drop trigger site_aggregates_user on user_;
+drop trigger site_aggregates_post on post;
+drop trigger site_aggregates_comment on comment;
+drop trigger site_aggregates_community on community;
+drop function 
+  site_aggregates_user,
+  site_aggregates_post,
+  site_aggregates_comment,
+  site_aggregates_community;
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 (file)
index 0000000..0a5d208
--- /dev/null
@@ -0,0 +1,95 @@
+-- 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()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set users = users + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates 
+    set users = users - 1;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_user
+after insert or delete on user_
+execute procedure site_aggregates_user();
+
+-- post
+create function site_aggregates_post()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set posts = posts + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates 
+    set posts = posts - 1;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_post
+after insert or delete on post
+for each row
+execute procedure site_aggregates_post();
+
+-- comment
+create function site_aggregates_comment()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set comments = comments + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates 
+    set comments = comments - 1;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_comment
+after insert or delete on comment
+for each row
+execute procedure site_aggregates_comment();
+
+-- community
+create function site_aggregates_community()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update site_aggregates 
+    set communities = communities + 1;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update site_aggregates 
+    set communities = communities - 1;
+  END IF;
+  return null;
+end $$;
+
+create trigger site_aggregates_community
+after insert or delete on community
+for each row
+execute procedure site_aggregates_community();
+
diff --git a/migrations/2020-12-03-035643_create_user_aggregates/down.sql b/migrations/2020-12-03-035643_create_user_aggregates/down.sql
new file mode 100644 (file)
index 0000000..a7b5e47
--- /dev/null
@@ -0,0 +1,13 @@
+-- User aggregates
+drop table user_aggregates;
+drop trigger user_aggregates_user on user_;
+drop trigger user_aggregates_post_count on post;
+drop trigger user_aggregates_post_score on post_like;
+drop trigger user_aggregates_comment_count on comment;
+drop trigger user_aggregates_comment_score on comment_like;
+drop function 
+  user_aggregates_user, 
+  user_aggregates_post_count,
+  user_aggregates_post_score,
+  user_aggregates_comment_count,
+  user_aggregates_comment_score;
diff --git a/migrations/2020-12-03-035643_create_user_aggregates/up.sql b/migrations/2020-12-03-035643_create_user_aggregates/up.sql
new file mode 100644 (file)
index 0000000..1bebfe3
--- /dev/null
@@ -0,0 +1,179 @@
+-- Add user aggregates
+create table user_aggregates (
+  id serial primary key,
+  user_id int references user_ on update cascade on delete cascade not null,
+  post_count bigint not null default 0,
+  post_score bigint not null default 0,
+  comment_count bigint not null default 0,
+  comment_score bigint not null default 0,
+  unique (user_id)
+);
+
+insert into user_aggregates (user_id, post_count, post_score, comment_count, comment_score)
+  select u.id,
+  coalesce(pd.posts, 0),
+  coalesce(pd.score, 0),
+  coalesce(cd.comments, 0),
+  coalesce(cd.score, 0)
+  from user_ u
+  left join (
+    select p.creator_id,
+      count(distinct p.id) as posts,
+      sum(pl.score) as score
+      from post p
+      left join post_like pl on p.id = pl.post_id
+      group by p.creator_id
+    ) pd on u.id = pd.creator_id
+  left join ( 
+    select c.creator_id,
+    count(distinct c.id) as comments,
+    sum(cl.score) as score
+    from comment c
+    left join comment_like cl on c.id = cl.comment_id
+    group by c.creator_id
+  ) cd on u.id = cd.creator_id;
+
+
+-- Add user aggregate triggers
+
+-- initial user add
+create function user_aggregates_user()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into user_aggregates (user_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from user_aggregates where user_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_user
+after insert or delete on user_
+for each row
+execute procedure user_aggregates_user();
+
+-- post count
+create function user_aggregates_post_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update user_aggregates 
+    set post_count = post_count + 1 where user_id = NEW.creator_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates 
+    set post_count = post_count - 1 where user_id = OLD.creator_id;
+
+    -- If the post gets deleted, the score calculation trigger won't fire, 
+    -- so you need to re-calculate
+    update user_aggregates ua
+    set post_score = pd.score
+    from (
+      select u.id,
+      coalesce(0, sum(pl.score)) as score
+      -- User join because posts could be empty
+      from user_ u 
+      left join post p on u.id = p.creator_id
+      left join post_like pl on p.id = pl.post_id
+      group by u.id
+    ) pd 
+    where ua.user_id = pd.id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_post_count
+after insert or delete on post
+for each row
+execute procedure user_aggregates_post_count();
+
+-- post score
+create function user_aggregates_post_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    -- TODO not sure if this is working right
+    -- Need to get the post creator, not the voter
+    update user_aggregates ua
+    set post_score = post_score + NEW.score
+    from post p
+    where ua.user_id = p.creator_id and p.id = NEW.post_id;
+    
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates ua
+    set post_score = post_score - OLD.score
+    from post p
+    where ua.user_id = p.creator_id and p.id = OLD.post_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_post_score
+after insert or delete on post_like
+for each row
+execute procedure user_aggregates_post_score();
+
+-- comment count
+create function user_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update user_aggregates 
+    set comment_count = comment_count + 1 where user_id = NEW.creator_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates 
+    set comment_count = comment_count - 1 where user_id = OLD.creator_id;
+
+    -- If the comment gets deleted, the score calculation trigger won't fire, 
+    -- so you need to re-calculate
+    update user_aggregates ua
+    set comment_score = cd.score
+    from (
+      select u.id,
+      coalesce(0, sum(cl.score)) as score
+      -- User join because comments could be empty
+      from user_ u 
+      left join comment c on u.id = c.creator_id
+      left join comment_like cl on c.id = cl.comment_id
+      group by u.id
+    ) cd 
+    where ua.user_id = cd.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_comment_count
+after insert or delete on comment
+for each row
+execute procedure user_aggregates_comment_count();
+
+-- comment score
+create function user_aggregates_comment_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    -- Need to get the post creator, not the voter
+    update user_aggregates ua
+    set comment_score = comment_score + NEW.score
+    from comment c
+    where ua.user_id = c.creator_id and c.id = NEW.comment_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update user_aggregates ua
+    set comment_score = comment_score - OLD.score
+    from comment c
+    where ua.user_id = c.creator_id and c.id = OLD.comment_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger user_aggregates_comment_score
+after insert or delete on comment_like
+for each row
+execute procedure user_aggregates_comment_score();
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 (file)
index 0000000..ac2872d
--- /dev/null
@@ -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 (file)
index 0000000..34274b0
--- /dev/null
@@ -0,0 +1,95 @@
+-- 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
+for each row
+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 from comment c join post p on p.id = c.post_id and p.id = NEW.post_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates 
+    set comments = comments - 1 from comment c join post p on p.id = c.post_id and p.id = OLD.post_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_comment_count
+after insert or delete on comment
+for each row
+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
+for each row
+execute procedure community_aggregates_subscriber_count();
+
index fc4a313724eca25014441bd82f921efe3dcfc73b..1d00556ec8fc57413a39a62787c5262afef5ecbb 100644 (file)
@@ -7,9 +7,9 @@ use lemmy_db::{
   comment_view::{ReplyQueryBuilder, ReplyView},
   community::Community,
   post_view::{PostQueryBuilder, PostView},
-  site_view::SiteView,
   user::User_,
   user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  views::site_view::SiteView,
   ListingType,
   SortType,
 };
@@ -96,13 +96,13 @@ async fn get_feed_data(
     .namespaces(RSS_NAMESPACE.to_owned())
     .title(&format!(
       "{} - {}",
-      site_view.name,
+      site_view.site.name,
       listing_type.to_string()
     ))
     .link(Settings::get().get_protocol_and_hostname())
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
@@ -175,7 +175,7 @@ fn get_feed_user(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - {}", site_view.name, user.name))
+    .title(&format!("{} - {}", site_view.site.name, user.name))
     .link(user_url)
     .items(items);
 
@@ -201,7 +201,7 @@ fn get_feed_community(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - {}", site_view.name, community.name))
+    .title(&format!("{} - {}", site_view.site.name, community.name))
     .link(community.actor_id)
     .items(items);
 
@@ -231,11 +231,11 @@ fn get_feed_front(
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - Subscribed", site_view.name))
+    .title(&format!("{} - Subscribed", site_view.site.name))
     .link(Settings::get().get_protocol_and_hostname())
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
@@ -261,14 +261,14 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
   let mut channel_builder = ChannelBuilder::default();
   channel_builder
     .namespaces(RSS_NAMESPACE.to_owned())
-    .title(&format!("{} - Inbox", site_view.name))
+    .title(&format!("{} - Inbox", site_view.site.name))
     .link(format!(
       "{}/inbox",
       Settings::get().get_protocol_and_hostname()
     ))
     .items(items);
 
-  if let Some(site_desc) = site_view.description {
+  if let Some(site_desc) = site_view.site.description {
     channel_builder.description(&site_desc);
   }
 
index 1d9525ef2ba21bea072ca240b034a4171082f3ff..9f63a523b1632dc9bc950d1b6dc7c4e51c4be040 100644 (file)
@@ -1,7 +1,7 @@
 use actix_web::{body::Body, error::ErrorBadRequest, *};
 use anyhow::anyhow;
 use lemmy_api::version;
-use lemmy_db::site_view::SiteView;
+use lemmy_db::views::site_view::SiteView;
 use lemmy_structs::blocking;
 use lemmy_utils::{settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -46,12 +46,11 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
     },
     protocols,
     usage: NodeInfoUsage {
-      users: NodeInfoUsers {
-        total: site_view.number_of_users,
-      },
-      local_posts: site_view.number_of_posts,
-      local_comments: site_view.number_of_comments,
-      open_registrations: site_view.open_registration,
+      // TODO get these again
+      users: NodeInfoUsers { total: 0 },
+      local_posts: 0,
+      local_comments: 0,
+      open_registrations: site_view.site.open_registration,
     },
   };