]> Untitled Git - lemmy.git/commitdiff
Merge branch 'main' into move_views_to_diesel
authorDessalines <tyhou13@gmx.com>
Sun, 13 Dec 2020 17:07:11 +0000 (12:07 -0500)
committerDessalines <tyhou13@gmx.com>
Sun, 13 Dec 2020 17:07:11 +0000 (12:07 -0500)
92 files changed:
docs/src/about/goals.md
lemmy_api/src/claims.rs
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/comment.rs
lemmy_apub/src/activities/receive/comment_undo.rs
lemmy_apub/src/activities/receive/community.rs
lemmy_apub/src/activities/receive/mod.rs
lemmy_apub/src/activities/receive/post.rs
lemmy_apub/src/activities/receive/post_undo.rs
lemmy_apub/src/activities/receive/private_message.rs
lemmy_apub/src/activities/send/comment.rs
lemmy_apub/src/activities/send/community.rs
lemmy_apub/src/activities/send/post.rs
lemmy_apub/src/activities/send/private_message.rs
lemmy_apub/src/activities/send/user.rs
lemmy_apub/src/activity_queue.rs
lemmy_apub/src/extensions/group_extensions.rs
lemmy_apub/src/fetcher.rs
lemmy_apub/src/http/comment.rs
lemmy_apub/src/http/community.rs
lemmy_apub/src/http/mod.rs
lemmy_apub/src/http/post.rs
lemmy_apub/src/http/user.rs
lemmy_apub/src/inbox/community_inbox.rs
lemmy_apub/src/inbox/mod.rs
lemmy_apub/src/inbox/receive_for_community.rs
lemmy_apub/src/inbox/shared_inbox.rs
lemmy_apub/src/inbox/user_inbox.rs
lemmy_apub/src/lib.rs
lemmy_apub/src/objects/comment.rs
lemmy_apub/src/objects/community.rs
lemmy_apub/src/objects/post.rs
lemmy_apub/src/objects/private_message.rs
lemmy_apub/src/objects/user.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/post_aggregates.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/comment_report.rs
lemmy_db/src/comment_view.rs
lemmy_db/src/community_view.rs [deleted file]
lemmy_db/src/lib.rs
lemmy_db/src/post_report.rs
lemmy_db/src/post_view.rs [deleted file]
lemmy_db/src/schema.rs
lemmy_db/src/site_view.rs [deleted file]
lemmy_db/src/source/activity.rs [moved from lemmy_db/src/activity.rs with 98% similarity]
lemmy_db/src/source/category.rs [moved from lemmy_db/src/category.rs with 89% similarity]
lemmy_db/src/source/comment.rs [moved from lemmy_db/src/comment.rs with 98% similarity]
lemmy_db/src/source/community.rs [moved from lemmy_db/src/community.rs with 89% similarity]
lemmy_db/src/source/mod.rs [new file with mode: 0644]
lemmy_db/src/source/moderator.rs [moved from lemmy_db/src/moderator.rs with 99% similarity]
lemmy_db/src/source/password_reset_request.rs [moved from lemmy_db/src/password_reset_request.rs with 98% similarity]
lemmy_db/src/source/post.rs [moved from lemmy_db/src/post.rs with 98% similarity]
lemmy_db/src/source/private_message.rs [moved from lemmy_db/src/private_message.rs with 99% similarity]
lemmy_db/src/source/site.rs [moved from lemmy_db/src/site.rs with 95% similarity]
lemmy_db/src/source/user.rs [moved from lemmy_db/src/user.rs with 85% similarity]
lemmy_db/src/source/user_mention.rs [moved from lemmy_db/src/user_mention.rs with 98% similarity]
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/post_view.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/lib.rs
lemmy_structs/src/post.rs
lemmy_structs/src/site.rs
lemmy_structs/src/user.rs
lemmy_websocket/src/chat_server.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]
migrations/2020-12-10-152350_create_post_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-10-152350_create_post_aggregates/up.sql [new file with mode: 0644]
src/code_migrations.rs
src/routes/feeds.rs
src/routes/nodeinfo.rs
src/routes/webfinger.rs
tests/integration_test.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 f475f1dfe763d4a8d8aa4bf5cdbeae78308c7118..4a0ab12b81c93509b983af031df54c3354e74ed6 100644 (file)
@@ -1,5 +1,5 @@
 use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_utils::settings::Settings;
 use serde::{Deserialize, Serialize};
 
index e74fa808e6ef30ce76e074156d0a3b0463cf213d..fe5fe859aa81657f1258bd29367ec53d6cb046d0 100644 (file)
@@ -10,13 +10,10 @@ use crate::{
 use actix_web::web::Data;
 use lemmy_apub::{ApubLikeableType, ApubObjectType};
 use lemmy_db::{
-  comment::*,
   comment_report::*,
   comment_view::*,
-  moderator::*,
-  post::*,
-  site_view::*,
-  user::*,
+  source::{comment::*, moderator::*, post::*, user::*},
+  views::site_view::SiteView,
   Crud,
   Likeable,
   ListingType,
@@ -552,8 +549,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..b704d24b98ca568b6d35941d9762c01f8f333b9b 100644 (file)
@@ -10,16 +10,16 @@ use actix_web::web::Data;
 use anyhow::Context;
 use lemmy_apub::ActorType;
 use lemmy_db::{
-  comment::Comment,
   comment_view::CommentQueryBuilder,
-  community::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
   naive_now,
-  post::Post,
-  site::*,
-  user_view::*,
+  source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
+  views::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    community_view::{CommunityQueryBuilder, CommunityView},
+    user_view::UserViewSafe,
+  },
   ApubObject,
   Bannable,
   Crud,
@@ -96,7 +96,7 @@ impl Perform for GetCommunity {
       .unwrap_or(1);
 
     let res = GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online,
     };
@@ -203,9 +203,7 @@ impl Perform for CreateCommunity {
     })
     .await??;
 
-    Ok(CommunityResponse {
-      community: community_view,
-    })
+    Ok(CommunityResponse { community_view })
   }
 }
 
@@ -228,7 +226,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 +283,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 +337,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 +403,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 +438,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 +511,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 +630,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 +738,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 +769,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 +783,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 +828,7 @@ impl Perform for TransferCommunity {
 
     // Return the jwt
     Ok(GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online: 0,
     })
@@ -850,15 +841,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..92287f8d3139916ab173d9ac0ef2819d9647f125 100644 (file)
@@ -1,10 +1,12 @@
 use crate::claims::Claims;
 use actix_web::{web, web::Data};
 use lemmy_db::{
-  community::{Community, CommunityModerator},
-  community_view::CommunityUserBanView,
-  post::Post,
-  user::User_,
+  source::{
+    community::{Community, CommunityModerator},
+    post::Post,
+    user::User_,
+  },
+  views::community_user_ban_view::CommunityUserBanView,
   Crud,
   DbPool,
 };
index 298076f75bbdcf1dd4dc9d9981b9e7d36d98ef31..2b3b4b5b4e04084658179a693fd7f0dbf34b1f11 100644 (file)
@@ -11,13 +11,15 @@ 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::*,
+  source::{moderator::*, post::*},
+  views::{
+    community_moderator_view::CommunityModeratorView,
+    community_view::CommunityView,
+    post_view::{PostQueryBuilder, PostView},
+    site_view::SiteView,
+  },
   Crud,
   Likeable,
   ListingType,
@@ -142,7 +144,7 @@ impl Perform for CreatePost {
       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
     };
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::CreatePost,
@@ -187,13 +189,13 @@ impl Perform for GetPost {
     })
     .await??;
 
-    let community_id = post_view.community_id;
+    let community_id = post_view.community.id;
     let community = blocking(context.pool(), move |conn| {
       CommunityView::read(conn, community_id, user_id)
     })
     .await??;
 
-    let community_id = post_view.community_id;
+    let community_id = post_view.community.id;
     let moderators = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, community_id)
     })
@@ -207,7 +209,7 @@ impl Perform for GetPost {
 
     // Return the jwt
     Ok(GetPostResponse {
-      post: post_view,
+      post_view,
       comments,
       community,
       moderators,
@@ -246,13 +248,12 @@ impl Perform for GetPosts {
     let community_id = data.community_id;
     let community_name = data.community_name.to_owned();
     let posts = match blocking(context.pool(), move |conn| {
-      PostQueryBuilder::create(conn)
+      PostQueryBuilder::create(conn, user_id)
         .listing_type(&type_)
         .sort(&sort)
         .show_nsfw(show_nsfw)
         .for_community_id(community_id)
         .for_community_name(community_name)
-        .my_user_id(user_id)
         .page(page)
         .limit(limit)
         .list()
@@ -281,8 +282,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());
       }
     }
@@ -335,7 +336,7 @@ impl Perform for CreatePostLike {
       Err(_e) => return Err(APIError::err("couldnt_find_post").into()),
     };
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::CreatePostLike,
@@ -428,7 +429,7 @@ impl Perform for EditPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::EditPost,
@@ -484,7 +485,7 @@ impl Perform for DeletePost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::DeletePost,
@@ -551,7 +552,7 @@ impl Perform for RemovePost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::RemovePost,
@@ -609,7 +610,7 @@ impl Perform for LockPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::LockPost,
@@ -671,7 +672,7 @@ impl Perform for StickyPost {
     })
     .await??;
 
-    let res = PostResponse { post: post_view };
+    let res = PostResponse { post_view };
 
     context.chat_server().do_send(SendPost {
       op: UserOperation::StickyPost,
@@ -719,7 +720,7 @@ impl Perform for SavePost {
     })
     .await??;
 
-    Ok(PostResponse { post: post_view })
+    Ok(PostResponse { post_view })
   }
 }
 
@@ -769,19 +770,19 @@ impl Perform for CreatePostReport {
 
     let user_id = user.id;
     let post_id = data.post_id;
-    let post = blocking(context.pool(), move |conn| {
+    let post_view = blocking(context.pool(), move |conn| {
       PostView::read(&conn, post_id, None)
     })
     .await??;
 
-    check_community_ban(user_id, post.community_id, context.pool()).await?;
+    check_community_ban(user_id, post_view.community.id, context.pool()).await?;
 
     let report_form = PostReportForm {
       creator_id: user_id,
       post_id,
-      original_post_name: post.name,
-      original_post_url: post.url,
-      original_post_body: post.body,
+      original_post_name: post_view.post.name,
+      original_post_url: post_view.post.url,
+      original_post_body: post_view.post.body,
       reason: data.reason.to_owned(),
     };
 
@@ -806,7 +807,7 @@ impl Perform for CreatePostReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::CreatePostReport,
       response: report,
-      community_id: post.community_id,
+      community_id: post_view.community.id,
       websocket_id,
     });
 
index e4b1dd21330a8508eda289b448c7243555e24422..98c501f1a04f2d012b8894542366000c89d882cb 100644 (file)
@@ -10,17 +10,18 @@ use actix_web::web::Data;
 use anyhow::Context;
 use lemmy_apub::fetcher::search_by_apub_id;
 use lemmy_db::{
-  category::*,
+  aggregates::site_aggregates::SiteAggregates,
   comment_view::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
   moderator_views::*,
   naive_now,
-  post_view::*,
-  site::*,
-  site_view::*,
-  user_view::*,
+  source::{category::*, moderator::*, site::*},
+  views::{
+    community_view::CommunityQueryBuilder,
+    post_view::PostQueryBuilder,
+    site_view::SiteView,
+    user_view::{UserQueryBuilder, UserViewSafe},
+  },
   Crud,
   SearchType,
   SortType,
@@ -280,20 +281,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 +311,8 @@ impl Perform for GetSite {
         u
       });
 
+    let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
+
     Ok(GetSiteResponse {
       site: site_view,
       admins,
@@ -318,6 +321,7 @@ impl Perform for GetSite {
       version: version::VERSION.to_string(),
       my_user,
       federated_instances: linked_instances(context.pool()).await?,
+      counts,
     })
   }
 }
@@ -359,13 +363,12 @@ impl Perform for Search {
     match type_ {
       SearchType::Posts => {
         posts = blocking(context.pool(), move |conn| {
-          PostQueryBuilder::create(conn)
+          PostQueryBuilder::create(conn, user_id)
             .sort(&sort)
             .show_nsfw(true)
             .for_community_id(community_id)
             .for_community_name(community_name)
             .search_term(q)
-            .my_user_id(user_id)
             .page(page)
             .limit(limit)
             .list()
@@ -386,7 +389,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)
@@ -408,13 +411,12 @@ impl Perform for Search {
       }
       SearchType::All => {
         posts = blocking(context.pool(), move |conn| {
-          PostQueryBuilder::create(conn)
+          PostQueryBuilder::create(conn, user_id)
             .sort(&sort)
             .show_nsfw(true)
             .for_community_id(community_id)
             .for_community_name(community_name)
             .search_term(q)
-            .my_user_id(user_id)
             .page(page)
             .limit(limit)
             .list()
@@ -439,7 +441,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)
@@ -463,7 +465,7 @@ impl Perform for Search {
       }
       SearchType::Url => {
         posts = blocking(context.pool(), move |conn| {
-          PostQueryBuilder::create(conn)
+          PostQueryBuilder::create(conn, None)
             .sort(&sort)
             .show_nsfw(true)
             .for_community_id(community_id)
@@ -531,15 +533,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 +553,7 @@ impl Perform for TransferSite {
       version: version::VERSION.to_string(),
       my_user: Some(user),
       federated_instances: linked_instances(context.pool()).await?,
+      counts,
     })
   }
 }
@@ -587,8 +592,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..e8099af89d883e7ea729ee9c2023af73b12ce75a 100644 (file)
@@ -15,26 +15,31 @@ use captcha::{gen, Difficulty};
 use chrono::Duration;
 use lemmy_apub::ApubObjectType;
 use lemmy_db::{
-  comment::*,
   comment_report::CommentReportView,
   comment_view::*,
-  community::*,
-  community_view::*,
   diesel_option_overwrite,
-  moderator::*,
   naive_now,
-  password_reset_request::*,
-  post::*,
   post_report::PostReportView,
-  post_view::*,
-  private_message::*,
   private_message_view::*,
-  site::*,
-  site_view::*,
-  user::*,
-  user_mention::*,
+  source::{
+    comment::*,
+    community::*,
+    moderator::*,
+    password_reset_request::*,
+    post::*,
+    private_message::*,
+    site::*,
+    user::*,
+    user_mention::*,
+  },
   user_mention_view::*,
-  user_view::*,
+  views::{
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    post_view::PostQueryBuilder,
+    site_view::SiteView,
+    user_view::{UserViewDangerous, UserViewSafe},
+  },
   Crud,
   Followable,
   Joinable,
@@ -113,9 +118,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 +158,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,36 +495,52 @@ 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;
     let community_id = data.community_id;
 
     let (posts, comments) = blocking(context.pool(), move |conn| {
-      let mut posts_query = PostQueryBuilder::create(conn)
+      let mut posts_query = PostQueryBuilder::create(conn, user_id)
         .sort(&sort)
         .show_nsfw(show_nsfw)
         .saved_only(saved_only)
         .for_community_id(community_id)
-        .my_user_id(user_id)
         .page(page)
         .limit(limit);
 
@@ -556,7 +576,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 +623,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 +704,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 ff0fb8199f96bd8ab436f088462dee2de9154084..f545a0426cee94ec3692c367225316976a9f125b 100644 (file)
@@ -5,9 +5,11 @@ use activitystreams::{
 };
 use anyhow::Context;
 use lemmy_db::{
-  comment::{Comment, CommentLike, CommentLikeForm},
   comment_view::CommentView,
-  post::Post,
+  source::{
+    comment::{Comment, CommentLike, CommentLikeForm},
+    post::Post,
+  },
   Likeable,
 };
 use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs};
index 2ee8c6ea7bb529cb40131293da8612267a515829..bf91fe3dd9ad461f8bfdfa72f3f373312e637b40 100644 (file)
@@ -1,8 +1,8 @@
 use crate::activities::receive::get_actor_as_user;
 use activitystreams::activity::{Dislike, Like};
 use lemmy_db::{
-  comment::{Comment, CommentLike},
   comment_view::CommentView,
+  source::comment::{Comment, CommentLike},
   Likeable,
 };
 use lemmy_structs::{blocking, comment::CommentResponse};
index b1866283b64a538558f51ab83deb08479378740f..cacb54eefab5cfc677a6a9ffbf29ee8c991b5ef9 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::{source::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 1f17fe9f3bc958b1cd772ad1db7f712b0c43e977..a66ddfb950a2bb6ac2c1a8181c92e181058074a2 100644 (file)
@@ -5,7 +5,7 @@ use activitystreams::{
   error::DomainError,
 };
 use anyhow::{anyhow, Context};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
 use log::debug;
index 0bbf1eaf52b78288709d153a73d2dbfbd1b2b6a1..f09071129773077dcde6506cb992b9888f0c0017 100644 (file)
@@ -5,8 +5,8 @@ use activitystreams::{
 };
 use anyhow::Context;
 use lemmy_db::{
-  post::{Post, PostLike, PostLikeForm},
-  post_view::PostView,
+  source::post::{Post, PostLike, PostLikeForm},
+  views::post_view::PostView,
   Likeable,
 };
 use lemmy_structs::{blocking, post::PostResponse};
@@ -31,7 +31,7 @@ pub(crate) async fn receive_create_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePost,
@@ -60,7 +60,7 @@ pub(crate) async fn receive_update_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
@@ -98,7 +98,7 @@ pub(crate) async fn receive_like_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -136,7 +136,7 @@ pub(crate) async fn receive_dislike_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -163,7 +163,7 @@ pub(crate) async fn receive_delete_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
@@ -190,7 +190,7 @@ pub(crate) async fn receive_remove_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
index bcbb7fee9806859c875072295b05037e81d03225..6827ded05f325bd8c8c95e1f9b092904c7f2d459 100644 (file)
@@ -1,8 +1,8 @@
 use crate::activities::receive::get_actor_as_user;
 use activitystreams::activity::{Dislike, Like};
 use lemmy_db::{
-  post::{Post, PostLike},
-  post_view::PostView,
+  source::post::{Post, PostLike},
+  views::post_view::PostView,
   Likeable,
 };
 use lemmy_structs::{blocking, post::PostResponse};
@@ -30,7 +30,7 @@ pub(crate) async fn receive_undo_like_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -62,7 +62,7 @@ pub(crate) async fn receive_undo_dislike_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::CreatePostLike,
@@ -89,7 +89,7 @@ pub(crate) async fn receive_undo_delete_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
     post: res,
@@ -115,7 +115,7 @@ pub(crate) async fn receive_undo_remove_post(
   })
   .await??;
 
-  let res = PostResponse { post: post_view };
+  let res = PostResponse { post_view };
 
   context.chat_server().do_send(SendPost {
     op: UserOperation::EditPost,
index 25ccc520d1eecfe674d7cf34d141b48630d822eb..25d4c26c52a535d17475b24354a1657c7b8cac57 100644 (file)
@@ -13,7 +13,7 @@ use activitystreams::{
   public,
 };
 use anyhow::{anyhow, Context};
-use lemmy_db::{private_message::PrivateMessage, private_message_view::PrivateMessageView};
+use lemmy_db::{private_message_view::PrivateMessageView, source::private_message::PrivateMessage};
 use lemmy_structs::{blocking, user::PrivateMessageResponse};
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
@@ -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 744a1cddbfe33dedcf55aeefed56321c0629f12e..6b417c2591b7062eee496370aef6f35f109a86b4 100644 (file)
@@ -26,7 +26,11 @@ use activitystreams::{
 };
 use anyhow::anyhow;
 use itertools::Itertools;
-use lemmy_db::{comment::Comment, community::Community, post::Post, user::User_, Crud, DbPool};
+use lemmy_db::{
+  source::{comment::Comment, community::Community, post::Post, user::User_},
+  Crud,
+  DbPool,
+};
 use lemmy_structs::{blocking, WebFingerResponse};
 use lemmy_utils::{
   request::{retry, RecvError},
index 775f8c25fa8dd011990add83c61c1a7f0f59a634..8596fc4e3de3376992158c844e3075a968c96332 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::{
+  source::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 f6eabb0485655fe3a4cff2147241a9f6e103b989..ec1ca67deb97f36f7a7b0fb9b947568d3db5d539 100644 (file)
@@ -21,7 +21,10 @@ use activitystreams::{
   prelude::*,
   public,
 };
-use lemmy_db::{community::Community, post::Post, user::User_, Crud};
+use lemmy_db::{
+  source::{community::Community, post::Post, user::User_},
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index e8bc979a7917c674326d360fd37db0bf4cf39fd7..9abe70d663ec4d9955c86525b866f5ac8053bafb 100644 (file)
@@ -16,7 +16,10 @@ use activitystreams::{
   },
   prelude::*,
 };
-use lemmy_db::{private_message::PrivateMessage, user::User_, Crud};
+use lemmy_db::{
+  source::{private_message::PrivateMessage, user::User_},
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 8c539c4b0ceb5ec8eecfe0dcbc5f6ef06fc9f4c8..26c5a5d5611c23bd53976c05e96bed234f8675b6 100644 (file)
@@ -14,8 +14,10 @@ use activitystreams::{
   object::ObjectExt,
 };
 use lemmy_db::{
-  community::{Community, CommunityFollower, CommunityFollowerForm},
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower, CommunityFollowerForm},
+    user::User_,
+  },
   ApubObject,
   DbPool,
   Followable,
index 467802794f606f4c16b600257fead7a7e92f9fec..07990457c0df92c8f19feadd6d3fe09f3d3d34f2 100644 (file)
@@ -19,7 +19,10 @@ use background_jobs::{
   WorkerConfig,
 };
 use itertools::Itertools;
-use lemmy_db::{community::Community, user::User_, DbPool};
+use lemmy_db::{
+  source::{community::Community, user::User_},
+  DbPool,
+};
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
 use log::{debug, warn};
index cb15267200828ffe3001a3f1b410433b5868fdd8..ab7acf337d8c69672dce2caa06434218a720bcc1 100644 (file)
@@ -1,7 +1,7 @@
 use activitystreams::unparsed::UnparsedMutExt;
 use activitystreams_ext::UnparsedExtension;
 use diesel::PgConnection;
-use lemmy_db::{category::Category, Crud};
+use lemmy_db::{source::category::Category, Crud};
 use lemmy_utils::LemmyError;
 use serde::{Deserialize, Serialize};
 
index ad977f5b1b3a371ed9fb1d6aa6bffcf3eead9360..2d40673feb07fb78320c86e1a788a470267ba711 100644 (file)
@@ -13,15 +13,15 @@ use anyhow::{anyhow, Context};
 use chrono::NaiveDateTime;
 use diesel::result::Error::NotFound;
 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,
+  source::{
+    comment::Comment,
+    community::{Community, CommunityModerator, CommunityModeratorForm},
+    post::Post,
+    user::User_,
+  },
+  views::{community_view::CommunityView, post_view::PostView, user_view::UserViewSafe},
   ApubObject,
   Joinable,
   SearchType,
@@ -161,7 +161,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 bb3d13aceb1db8d7db6ce6baed577f16cc82c138..16f4119027dafda6cfadd6b8a02f29630d9191f6 100644 (file)
@@ -4,7 +4,7 @@ use crate::{
 };
 use actix_web::{body::Body, web, web::Path, HttpResponse};
 use diesel::result::Error::NotFound;
-use lemmy_db::{comment::Comment, Crud};
+use lemmy_db::{source::comment::Comment, Crud};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 0a90571ff0287bfbb00b8eceb5cd2e60875b2f15..3caaf6613b6190c0f009a2e162f4ae2e76398896 100644 (file)
@@ -9,7 +9,10 @@ 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::{
+  source::{community::Community, post::Post},
+  views::community_follower_view::CommunityFollowerView,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 4f31f6a5e768aee9e8c92d5db65e7593e984f321..fee15b773594c11ba85904ad43ffa4d6b77fb706 100644 (file)
@@ -1,6 +1,6 @@
 use crate::APUB_JSON_CONTENT_TYPE;
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::activity::Activity;
+use lemmy_db::source::activity::Activity;
 use lemmy_structs::blocking;
 use lemmy_utils::{settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
index 1d25ed958ecb15107a5d830613152f5c1019b285..563af8456d9d4234739979aeb942a6cd2b983bb1 100644 (file)
@@ -4,7 +4,7 @@ use crate::{
 };
 use actix_web::{body::Body, web, HttpResponse};
 use diesel::result::Error::NotFound;
-use lemmy_db::post::Post;
+use lemmy_db::source::post::Post;
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 1e546d953dd47026116b00b29c22f97b57a98ea5..8dcc8edabf7a889bd92176c21cb1046bcff13779 100644 (file)
@@ -9,7 +9,7 @@ use activitystreams::{
   collection::{CollectionExt, OrderedCollection},
 };
 use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::user::User_;
+use lemmy_db::source::user::User_;
 use lemmy_structs::blocking;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
index 4bdad2fadcc433dd273459be7b63e69cfc404e7f..82df26bea8d00aa3d1f1a2697f35032c0b3e1860 100644 (file)
@@ -27,9 +27,11 @@ use activitystreams::{
 use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::{anyhow, Context};
 use lemmy_db::{
-  community::{Community, CommunityFollower, CommunityFollowerForm},
-  community_view::CommunityUserBanView,
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower, CommunityFollowerForm},
+    user::User_,
+  },
+  views::community_user_ban_view::CommunityUserBanView,
   ApubObject,
   DbPool,
   Followable,
@@ -82,7 +84,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..415851883d8bc78a652cabf5d8d6eb2cbf413517 100644 (file)
@@ -12,7 +12,11 @@ use activitystreams::{
 };
 use actix_web::HttpRequest;
 use anyhow::{anyhow, Context};
-use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool};
+use lemmy_db::{
+  source::{activity::Activity, community::Community, user::User_},
+  ApubObject,
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -50,7 +54,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 +79,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 eaad6b1ccbf2e0301c574f231e63c8a335268286..73deb97176e832b1c89e134c975e12828f1dfce9 100644 (file)
@@ -41,7 +41,11 @@ use activitystreams::{
 };
 use anyhow::Context;
 use diesel::result::Error::NotFound;
-use lemmy_db::{comment::Comment, post::Post, site::Site, ApubObject, Crud};
+use lemmy_db::{
+  source::{comment::Comment, post::Post, site::Site},
+  ApubObject,
+  Crud,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
index 826038bf09ccbd37ee5f406dd1c0acb20acd225c..d94c54f253a0284f9b3a8bb16db9dc56f49375c4 100644 (file)
@@ -15,7 +15,7 @@ use crate::{
 use activitystreams::{activity::ActorAndObject, prelude::*};
 use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::Context;
-use lemmy_db::{community::Community, ApubObject, DbPool};
+use lemmy_db::{source::community::Community, ApubObject, DbPool};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, LemmyError};
 use lemmy_websocket::LemmyContext;
@@ -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..374772d6d99986fc5883041d4dd2db6ab537f88c 100644 (file)
@@ -49,9 +49,11 @@ use actix_web::{web, HttpRequest, HttpResponse};
 use anyhow::{anyhow, Context};
 use diesel::NotFound;
 use lemmy_db::{
-  community::{Community, CommunityFollower},
-  private_message::PrivateMessage,
-  user::User_,
+  source::{
+    community::{Community, CommunityFollower},
+    private_message::PrivateMessage,
+    user::User_,
+  },
   ApubObject,
   Followable,
 };
@@ -102,7 +104,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 +175,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 9b933b6e0c8b2cd61078985adb9baa12e09b74a7..78224c32ebd1ed867b970d6bcf176863ded76578 100644 (file)
@@ -22,7 +22,10 @@ use activitystreams::{
 };
 use activitystreams_ext::{Ext1, Ext2};
 use anyhow::{anyhow, Context};
-use lemmy_db::{activity::Activity, user::User_, DbPool};
+use lemmy_db::{
+  source::{activity::Activity, user::User_},
+  DbPool,
+};
 use lemmy_structs::blocking;
 use lemmy_utils::{location_info, settings::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
index 56d75a404d691c095ab17175c72ab6a610e19ead..957966d7fe2abac86318ce596a6b01d11b60e361 100644 (file)
@@ -24,10 +24,12 @@ use activitystreams::{
 };
 use anyhow::{anyhow, Context};
 use lemmy_db::{
-  comment::{Comment, CommentForm},
-  community::Community,
-  post::Post,
-  user::User_,
+  source::{
+    comment::{Comment, CommentForm},
+    community::Community,
+    post::Post,
+    user::User_,
+  },
   Crud,
   DbPool,
 };
index 594a5b5ee03956a1a574be545e8c8f1e88f89c12..3bd47560efefeeafa638a2964a94a57244e3cda0 100644 (file)
@@ -23,9 +23,9 @@ use activitystreams::{
 use activitystreams_ext::Ext2;
 use anyhow::Context;
 use lemmy_db::{
-  community::{Community, CommunityForm},
-  community_view::CommunityModeratorView,
   naive_now,
+  source::community::{Community, CommunityForm},
+  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
index 39b749972342712afbaf0c59edaecdd2707a1e51..9c9df5b1962cb3a2a97519f59d34d46b3bbcea90 100644 (file)
@@ -21,9 +21,11 @@ use activitystreams::{
 use activitystreams_ext::Ext1;
 use anyhow::Context;
 use lemmy_db::{
-  community::Community,
-  post::{Post, PostForm},
-  user::User_,
+  source::{
+    community::Community,
+    post::{Post, PostForm},
+    user::User_,
+  },
   Crud,
   DbPool,
 };
index ec8b55e7e76a49ce4a9c31a1dc1909487b1b4b6d..e69c28110d4dee60a8912cedf933733146cdbf2f 100644 (file)
@@ -20,8 +20,10 @@ use activitystreams::{
 };
 use anyhow::Context;
 use lemmy_db::{
-  private_message::{PrivateMessage, PrivateMessageForm},
-  user::User_,
+  source::{
+    private_message::{PrivateMessage, PrivateMessageForm},
+    user::User_,
+  },
   Crud,
   DbPool,
 };
index 18490796aec12e9930ac1ad1ac99af473dd04b87..8c3312d1f8f8a27ac79e303f4ae479f5fb94e8c1 100644 (file)
@@ -20,7 +20,7 @@ use activitystreams_ext::Ext1;
 use anyhow::Context;
 use lemmy_db::{
   naive_now,
-  user::{UserForm, User_},
+  source::user::{UserForm, User_},
   ApubObject,
   DbPool,
 };
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..8c977bf
--- /dev/null
@@ -0,0 +1,268 @@
+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 comments: i64,
+}
+
+impl CommunityAggregates {
+  pub fn read(conn: &PgConnection, community_id: i32) -> Result<Self, Error> {
+    community_aggregates::table
+      .filter(community_aggregates::community_id.eq(community_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::community_aggregates::CommunityAggregates,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm},
+      post::{Post, PostForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Followable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_community_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_community_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_community_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 another_community = CommunityForm {
+      name: "TIL_community_agg_2".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 another_inserted_community = Community::create(&conn, &another_community).unwrap();
+
+    let first_user_follow = CommunityFollowerForm {
+      community_id: inserted_community.id,
+      user_id: inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &first_user_follow).unwrap();
+
+    let second_user_follow = CommunityFollowerForm {
+      community_id: inserted_community.id,
+      user_id: another_inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &second_user_follow).unwrap();
+
+    let another_community_follow = CommunityFollowerForm {
+      community_id: another_inserted_community.id,
+      user_id: inserted_user.id,
+      pending: false,
+    };
+
+    CommunityFollower::follow(&conn, &another_community_follow).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 community_aggregates_before_delete =
+      CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+
+    assert_eq!(2, community_aggregates_before_delete.subscribers);
+    assert_eq!(1, community_aggregates_before_delete.posts);
+    assert_eq!(2, community_aggregates_before_delete.comments);
+
+    // Test the other community
+    let another_community_aggs =
+      CommunityAggregates::read(&conn, another_inserted_community.id).unwrap();
+    assert_eq!(1, another_community_aggs.subscribers);
+    assert_eq!(0, another_community_aggs.posts);
+    assert_eq!(0, another_community_aggs.comments);
+
+    // Unfollow test
+    CommunityFollower::unfollow(&conn, &second_user_follow).unwrap();
+    let after_unfollow = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(1, after_unfollow.subscribers);
+
+    // Follow again just for the later tests
+    CommunityFollower::follow(&conn, &second_user_follow).unwrap();
+    let after_follow_again = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(2, after_follow_again.subscribers);
+
+    // Remove a parent comment (the comment count should also be 0)
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let after_parent_post_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(0, after_parent_post_delete.comments);
+    assert_eq!(0, after_parent_post_delete.posts);
+
+    // Remove the 2nd user
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+    let after_user_delete = CommunityAggregates::read(&conn, inserted_community.id).unwrap();
+    assert_eq!(1, after_user_delete.subscribers);
+
+    // 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);
+
+    // Should be none found, since the creator was deleted
+    let after_delete = CommunityAggregates::read(&conn, inserted_community.id);
+    assert!(after_delete.is_err());
+  }
+}
diff --git a/lemmy_db/src/aggregates/mod.rs b/lemmy_db/src/aggregates/mod.rs
new file mode 100644 (file)
index 0000000..e033aed
--- /dev/null
@@ -0,0 +1,4 @@
+pub mod community_aggregates;
+pub mod post_aggregates;
+pub mod site_aggregates;
+pub mod user_aggregates;
diff --git a/lemmy_db/src/aggregates/post_aggregates.rs b/lemmy_db/src/aggregates/post_aggregates.rs
new file mode 100644 (file)
index 0000000..dff16f9
--- /dev/null
@@ -0,0 +1,237 @@
+use crate::schema::post_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "post_aggregates"]
+pub struct PostAggregates {
+  pub id: i32,
+  pub post_id: i32,
+  pub comments: i64,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+  pub newest_comment_time: chrono::NaiveDateTime,
+}
+
+impl PostAggregates {
+  pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+    post_aggregates::table
+      .filter(post_aggregates::post_id.eq(post_id))
+      .first::<Self>(conn)
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::post_aggregates::PostAggregates,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm, PostLike, PostLikeForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    Crud,
+    Likeable,
+    ListingType,
+    SortType,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "thommy_community_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_community_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_community_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 post_like = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    PostLike::like(&conn, &post_like).unwrap();
+
+    let post_aggs_before_delete = PostAggregates::read(&conn, inserted_post.id).unwrap();
+
+    assert_eq!(2, post_aggs_before_delete.comments);
+    assert_eq!(1, post_aggs_before_delete.score);
+    assert_eq!(1, post_aggs_before_delete.upvotes);
+    assert_eq!(0, post_aggs_before_delete.downvotes);
+
+    // Add a post dislike from the other user
+    let post_dislike = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: another_inserted_user.id,
+      score: -1,
+    };
+
+    PostLike::like(&conn, &post_dislike).unwrap();
+
+    let post_aggs_after_dislike = PostAggregates::read(&conn, inserted_post.id).unwrap();
+
+    assert_eq!(2, post_aggs_after_dislike.comments);
+    assert_eq!(0, post_aggs_after_dislike.score);
+    assert_eq!(1, post_aggs_after_dislike.upvotes);
+    assert_eq!(1, post_aggs_after_dislike.downvotes);
+
+    // Remove the parent comment
+    Comment::delete(&conn, inserted_comment.id).unwrap();
+    let after_comment_delete = PostAggregates::read(&conn, inserted_post.id).unwrap();
+    assert_eq!(0, after_comment_delete.comments);
+    assert_eq!(0, after_comment_delete.score);
+    assert_eq!(1, after_comment_delete.upvotes);
+    assert_eq!(1, after_comment_delete.downvotes);
+
+    // Remove the first post like
+    PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let after_like_remove = PostAggregates::read(&conn, inserted_post.id).unwrap();
+    assert_eq!(0, after_like_remove.comments);
+    assert_eq!(-1, after_like_remove.score);
+    assert_eq!(0, after_like_remove.upvotes);
+    assert_eq!(1, after_like_remove.downvotes);
+
+    // This should delete all the associated rows, and fire triggers
+    User_::delete(&conn, another_inserted_user.id).unwrap();
+    let user_num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
+    assert_eq!(1, user_num_deleted);
+
+    // Should be none found, since the creator was deleted
+    let after_delete = PostAggregates::read(&conn, inserted_post.id);
+    assert!(after_delete.is_err());
+  }
+}
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..6856bfc
--- /dev/null
@@ -0,0 +1,173 @@
+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,
+    source::{
+      comment::{Comment, CommentForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    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,
+    };
+
+    // Insert two of those posts
+    let inserted_post = Post::create(&conn, &new_post).unwrap();
+    let _inserted_post_again = 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,
+    };
+
+    // Insert two of those comments
+    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!(2, site_aggregates_before_delete.posts);
+    assert_eq!(2, site_aggregates_before_delete.comments);
+
+    // Try a post delete
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let site_aggregates_after_post_delete = SiteAggregates::read(&conn).unwrap();
+    assert_eq!(1, site_aggregates_after_post_delete.posts);
+    assert_eq!(0, site_aggregates_after_post_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..104bf6f
--- /dev/null
@@ -0,0 +1,250 @@
+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,
+    source::{
+      comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
+      community::{Community, CommunityForm},
+      post::{Post, PostForm, PostLike, PostLikeForm},
+      user::{UserForm, User_},
+    },
+    tests::establish_unpooled_connection,
+    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 mut 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 mut 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);
+
+    // Add in the two comments again, then delete the post.
+    let new_parent_comment = Comment::create(&conn, &comment_form).unwrap();
+    child_comment_form.parent_id = Some(new_parent_comment.id);
+    Comment::create(&conn, &child_comment_form).unwrap();
+    comment_like.comment_id = new_parent_comment.id;
+    CommentLike::like(&conn, &comment_like).unwrap();
+    let after_comment_add = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(2, after_comment_add.comment_count);
+    assert_eq!(1, after_comment_add.comment_score);
+
+    Post::delete(&conn, inserted_post.id).unwrap();
+    let after_post_delete = UserAggregates::read(&conn, inserted_user.id).unwrap();
+    assert_eq!(0, after_post_delete.comment_score);
+    assert_eq!(0, after_post_delete.comment_count);
+    assert_eq!(0, after_post_delete.post_score);
+    assert_eq!(0, after_post_delete.post_count);
+
+    // 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 a243891eb8d863ea9890db4c0157cbbb6cd29ce2..240b73430702a0be7228faf56a72df6972a2bf8a 100644 (file)
@@ -2,10 +2,10 @@ use diesel::{dsl::*, pg::Pg, result::Error, *};
 use serde::{Deserialize, Serialize};
 
 use crate::{
-  comment::Comment,
   limit_and_offset,
   naive_now,
   schema::comment_report,
+  source::comment::Comment,
   MaybeOptional,
   Reportable,
 };
index 4b6dc19248f725df149af25fd7f6d4f15270c855..f463168b6e30c60d9b4696c11a78a65b3b4ca02c 100644 (file)
@@ -497,12 +497,9 @@ impl<'a> ReplyQueryBuilder<'a> {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
     comment_view::*,
-    community::*,
-    post::*,
+    source::{comment::*, community::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     Crud,
     Likeable,
     *,
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..098a88e4acfce1361c4641ff46ce004289d998ea 100644 (file)
@@ -11,28 +11,17 @@ use regex::Regex;
 use serde::{Deserialize, Serialize};
 use std::{env, env::VarError};
 
-pub mod activity;
-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;
-pub mod post;
 pub mod post_report;
-pub mod post_view;
-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 aggregates;
+pub mod schema;
+pub mod source;
+pub mod views;
 
 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
 
@@ -149,6 +138,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 +216,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 5f8aa5ea59f263468da145517a9a79d3860a29f2..230368c5c51d9d27579a832b31fefb81091a21ae 100644 (file)
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
 use crate::{
   limit_and_offset,
   naive_now,
-  post::Post,
   schema::post_report,
+  source::post::Post,
   MaybeOptional,
   Reportable,
 };
diff --git a/lemmy_db/src/post_view.rs b/lemmy_db/src/post_view.rs
deleted file mode 100644 (file)
index ea03c3a..0000000
+++ /dev/null
@@ -1,641 +0,0 @@
-use super::post_view::post_fast_view::BoxedQuery;
-use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-// The faked schema since diesel doesn't do views
-table! {
-  post_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    url -> Nullable<Text>,
-    body -> Nullable<Text>,
-    creator_id -> Int4,
-    community_id -> Int4,
-    removed -> Bool,
-    locked -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    stickied -> Bool,
-    embed_title -> Nullable<Text>,
-    embed_description -> Nullable<Text>,
-    embed_html -> Nullable<Text>,
-    thumbnail_url -> Nullable<Text>,
-    ap_id -> Text,
-    local -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    community_removed -> Bool,
-    community_deleted -> Bool,
-    community_nsfw -> Bool,
-    number_of_comments -> BigInt,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    newest_activity_time -> Timestamp,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    read -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-table! {
-  post_fast_view (id) {
-    id -> Int4,
-    name -> Varchar,
-    url -> Nullable<Text>,
-    body -> Nullable<Text>,
-    creator_id -> Int4,
-    community_id -> Int4,
-    removed -> Bool,
-    locked -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    nsfw -> Bool,
-    stickied -> Bool,
-    embed_title -> Nullable<Text>,
-    embed_description -> Nullable<Text>,
-    embed_html -> Nullable<Text>,
-    thumbnail_url -> Nullable<Text>,
-    ap_id -> Text,
-    local -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    community_removed -> Bool,
-    community_deleted -> Bool,
-    community_nsfw -> Bool,
-    number_of_comments -> BigInt,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    newest_activity_time -> Timestamp,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    read -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "post_fast_view"]
-pub struct PostView {
-  pub id: i32,
-  pub name: String,
-  pub url: Option<String>,
-  pub body: Option<String>,
-  pub creator_id: i32,
-  pub community_id: i32,
-  pub removed: bool,
-  pub locked: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub nsfw: bool,
-  pub stickied: bool,
-  pub embed_title: Option<String>,
-  pub embed_description: Option<String>,
-  pub embed_html: Option<String>,
-  pub thumbnail_url: Option<String>,
-  pub ap_id: String,
-  pub local: bool,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_published: chrono::NaiveDateTime,
-  pub creator_avatar: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub community_removed: bool,
-  pub community_deleted: bool,
-  pub community_nsfw: bool,
-  pub number_of_comments: i64,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub newest_activity_time: chrono::NaiveDateTime,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub read: Option<bool>,
-  pub saved: Option<bool>,
-}
-
-pub struct PostQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: BoxedQuery<'a, Pg>,
-  listing_type: &'a ListingType,
-  sort: &'a SortType,
-  my_user_id: Option<i32>,
-  for_creator_id: Option<i32>,
-  for_community_id: Option<i32>,
-  for_community_name: Option<String>,
-  search_term: Option<String>,
-  url_search: Option<String>,
-  show_nsfw: bool,
-  saved_only: bool,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> PostQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::post_view::post_fast_view::dsl::*;
-
-    let query = post_fast_view.into_boxed();
-
-    PostQueryBuilder {
-      conn,
-      query,
-      listing_type: &ListingType::All,
-      sort: &SortType::Hot,
-      my_user_id: None,
-      for_creator_id: None,
-      for_community_id: None,
-      for_community_name: None,
-      search_term: None,
-      url_search: None,
-      show_nsfw: true,
-      saved_only: false,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
-    self.listing_type = listing_type;
-    self
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
-    self.for_community_id = for_community_id.get_optional();
-    self
-  }
-
-  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
-    self.for_community_name = for_community_name.get_optional();
-    self
-  }
-
-  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
-    self.for_creator_id = for_creator_id.get_optional();
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    self
-  }
-
-  pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
-    self.url_search = url_search.get_optional();
-    self
-  }
-
-  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
-    self.my_user_id = my_user_id.get_optional();
-    self
-  }
-
-  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
-    self.show_nsfw = show_nsfw;
-    self
-  }
-
-  pub fn saved_only(mut self, saved_only: bool) -> Self {
-    self.saved_only = saved_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<PostView>, Error> {
-    use super::post_view::post_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    query = match self.listing_type {
-      ListingType::Subscribed => query.filter(subscribed.eq(true)),
-      ListingType::Local => query.filter(community_local.eq(true)),
-      _ => query,
-    };
-
-    if let Some(for_community_id) = self.for_community_id {
-      query = query
-        .filter(community_id.eq(for_community_id))
-        .then_order_by(stickied.desc());
-    }
-
-    if let Some(for_community_name) = self.for_community_name {
-      query = query
-        .filter(community_name.eq(for_community_name))
-        .filter(community_local.eq(true))
-        .then_order_by(stickied.desc());
-    }
-
-    if let Some(url_search) = self.url_search {
-      query = query.filter(url.eq(url_search));
-    }
-
-    if let Some(search_term) = self.search_term {
-      let searcher = fuzzy_search(&search_term);
-      query = query.filter(name.ilike(searcher.to_owned()).or(body.ilike(searcher)));
-    }
-
-    query = match self.sort {
-      SortType::Active => query
-        .then_order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::Hot => query
-        .then_order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.then_order_by(published.desc()),
-      SortType::TopAll => query.then_order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .then_order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .then_order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .then_order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .then_order_by(score.desc()),
-    };
-
-    // The view lets you pass a null user_id, if you're not logged in
-    query = if let Some(my_user_id) = self.my_user_id {
-      query.filter(user_id.eq(my_user_id))
-    } else {
-      query.filter(user_id.is_null())
-    };
-
-    // If its for a specific user, show the removed / deleted
-    if let Some(for_creator_id) = self.for_creator_id {
-      query = query.filter(creator_id.eq(for_creator_id));
-    } else {
-      query = query
-        .filter(removed.eq(false))
-        .filter(deleted.eq(false))
-        .filter(community_removed.eq(false))
-        .filter(community_deleted.eq(false));
-    }
-
-    if !self.show_nsfw {
-      query = query
-        .filter(nsfw.eq(false))
-        .filter(community_nsfw.eq(false));
-    };
-
-    // TODO these are wrong, bc they'll only show saved for your logged in user, not theirs
-    if self.saved_only {
-      query = query.filter(saved.eq(true));
-    };
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query = query
-      .limit(limit)
-      .offset(offset)
-      .filter(removed.eq(false))
-      .filter(deleted.eq(false))
-      .filter(community_removed.eq(false))
-      .filter(community_deleted.eq(false));
-
-    query.load::<PostView>(self.conn)
-  }
-}
-
-impl PostView {
-  pub fn read(
-    conn: &PgConnection,
-    from_post_id: i32,
-    my_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::post_view::post_fast_view::dsl::*;
-    use diesel::prelude::*;
-
-    let mut query = post_fast_view.into_boxed();
-
-    query = query.filter(id.eq(from_post_id));
-
-    if let Some(my_user_id) = my_user_id {
-      query = query.filter(user_id.eq(my_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    };
-
-    query.first::<Self>(conn)
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use crate::{
-    community::*,
-    post::*,
-    post_view::*,
-    tests::establish_unpooled_connection,
-    user::*,
-    Crud,
-    Likeable,
-    *,
-  };
-
-  #[test]
-  fn test_crud() {
-    let conn = establish_unpooled_connection();
-
-    let user_name = "tegan".to_string();
-    let community_name = "test_community_3".to_string();
-    let post_name = "test post 3".to_string();
-
-    let new_user = UserForm {
-      name: user_name.to_owned(),
-      preferred_username: None,
-      password_encrypted: "nope".into(),
-      email: None,
-      matrix_user_id: None,
-      avatar: None,
-      banner: None,
-      published: None,
-      updated: None,
-      admin: false,
-      banned: Some(false),
-      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: community_name.to_owned(),
-      title: "nada".to_owned(),
-      description: None,
-      creator_id: inserted_user.id,
-      category_id: 1,
-      removed: None,
-      deleted: None,
-      updated: None,
-      nsfw: false,
-      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: post_name.to_owned(),
-      url: None,
-      body: None,
-      creator_id: inserted_user.id,
-      community_id: inserted_community.id,
-      removed: None,
-      deleted: None,
-      locked: None,
-      stickied: None,
-      updated: None,
-      nsfw: false,
-      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_form = PostLikeForm {
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      score: 1,
-    };
-
-    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
-
-    let expected_post_like = PostLike {
-      id: inserted_post_like.id,
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      published: inserted_post_like.published,
-      score: 1,
-    };
-
-    let read_post_listings_with_user = PostQueryBuilder::create(&conn)
-      .listing_type(&ListingType::Community)
-      .sort(&SortType::New)
-      .for_community_id(inserted_community.id)
-      .my_user_id(inserted_user.id)
-      .list()
-      .unwrap();
-
-    let read_post_listings_no_user = PostQueryBuilder::create(&conn)
-      .listing_type(&ListingType::Community)
-      .sort(&SortType::New)
-      .for_community_id(inserted_community.id)
-      .list()
-      .unwrap();
-
-    let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
-    let read_post_listing_with_user =
-      PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
-
-    // the non user version
-    let expected_post_listing_no_user = PostView {
-      user_id: None,
-      my_vote: None,
-      id: inserted_post.id,
-      name: post_name.to_owned(),
-      url: None,
-      body: None,
-      creator_id: inserted_user.id,
-      creator_name: user_name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      banned: false,
-      banned_from_community: false,
-      community_id: inserted_community.id,
-      removed: false,
-      deleted: false,
-      locked: false,
-      stickied: false,
-      community_name: community_name.to_owned(),
-      community_icon: None,
-      community_removed: false,
-      community_deleted: false,
-      community_nsfw: false,
-      number_of_comments: 0,
-      score: 1,
-      upvotes: 1,
-      downvotes: 0,
-      hot_rank: read_post_listing_no_user.hot_rank,
-      hot_rank_active: read_post_listing_no_user.hot_rank_active,
-      published: inserted_post.published,
-      newest_activity_time: inserted_post.published,
-      updated: None,
-      subscribed: None,
-      read: None,
-      saved: None,
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: inserted_post.ap_id.to_owned(),
-      local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-    };
-
-    let expected_post_listing_with_user = PostView {
-      user_id: Some(inserted_user.id),
-      my_vote: Some(1),
-      id: inserted_post.id,
-      name: post_name,
-      url: None,
-      body: None,
-      removed: false,
-      deleted: false,
-      locked: false,
-      stickied: false,
-      creator_id: inserted_user.id,
-      creator_name: user_name,
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      banned: false,
-      banned_from_community: false,
-      community_id: inserted_community.id,
-      community_name,
-      community_icon: None,
-      community_removed: false,
-      community_deleted: false,
-      community_nsfw: false,
-      number_of_comments: 0,
-      score: 1,
-      upvotes: 1,
-      downvotes: 0,
-      hot_rank: read_post_listing_with_user.hot_rank,
-      hot_rank_active: read_post_listing_with_user.hot_rank_active,
-      published: inserted_post.published,
-      newest_activity_time: inserted_post.published,
-      updated: None,
-      subscribed: Some(false),
-      read: Some(false),
-      saved: Some(false),
-      nsfw: false,
-      embed_title: None,
-      embed_description: None,
-      embed_html: None,
-      thumbnail_url: None,
-      ap_id: inserted_post.ap_id.to_owned(),
-      local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-    };
-
-    let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
-    let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
-    Community::delete(&conn, inserted_community.id).unwrap();
-    User_::delete(&conn, inserted_user.id).unwrap();
-
-    // The with user
-    assert_eq!(
-      expected_post_listing_with_user,
-      read_post_listings_with_user[0]
-    );
-    assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
-    assert_eq!(1, read_post_listings_with_user.len());
-
-    // Without the user
-    assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
-    assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
-    assert_eq!(1, read_post_listings_no_user.len());
-
-    // assert_eq!(expected_post, inserted_post);
-    // assert_eq!(expected_post, updated_post);
-    assert_eq!(expected_post_like, inserted_post_like);
-    assert_eq!(1, like_removed);
-    assert_eq!(1, num_deleted);
-  }
-}
index 49bbc46fb1c74fa29af44032c3d603ca43efc73d..b0c57f5e15e23bb919f526b0a8befbb79ba58eb5 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,
@@ -319,6 +329,18 @@ table! {
     }
 }
 
+table! {
+    post_aggregates (id) {
+        id -> Int4,
+        post_id -> Int4,
+        comments -> Int8,
+        score -> Int8,
+        upvotes -> Int8,
+        downvotes -> Int8,
+        newest_comment_time -> Timestamp,
+    }
+}
+
 table! {
     post_aggregates_fast (id) {
         id -> Int4,
@@ -440,6 +462,16 @@ table! {
     }
 }
 
+table! {
+    site_aggregates (id) {
+        id -> Int4,
+        users -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+        communities -> Int8,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -471,6 +503,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 +566,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));
@@ -544,6 +588,7 @@ joinable!(mod_sticky_post -> user_ (mod_user_id));
 joinable!(password_reset_request -> user_ (user_id));
 joinable!(post -> community (community_id));
 joinable!(post -> user_ (creator_id));
+joinable!(post_aggregates -> post (post_id));
 joinable!(post_like -> post (post_id));
 joinable!(post_like -> user_ (user_id));
 joinable!(post_read -> post (post_id));
@@ -552,6 +597,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 +611,7 @@ allow_tables_to_appear_in_same_query!(
   comment_report,
   comment_saved,
   community,
+  community_aggregates,
   community_aggregates_fast,
   community_follower,
   community_moderator,
@@ -580,6 +627,7 @@ allow_tables_to_appear_in_same_query!(
   mod_sticky_post,
   password_reset_request,
   post,
+  post_aggregates,
   post_aggregates_fast,
   post_like,
   post_read,
@@ -587,7 +635,9 @@ allow_tables_to_appear_in_same_query!(
   post_saved,
   private_message,
   site,
+  site_aggregates,
   user_,
+  user_aggregates,
   user_ban,
   user_fast,
   user_mention,
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)
-  }
-}
similarity index 98%
rename from lemmy_db/src/activity.rs
rename to lemmy_db/src/source/activity.rs
index 190dd411c5c37e60fabd7b082131335e506d9fb2..b4b54c6ed293ad2681af14e75b16bfad4102eeaa 100644 (file)
@@ -97,9 +97,11 @@ impl Activity {
 #[cfg(test)]
 mod tests {
   use crate::{
-    activity::{Activity, ActivityForm},
+    source::{
+      activity::{Activity, ActivityForm},
+      user::{UserForm, User_},
+    },
     tests::establish_unpooled_connection,
-    user::{UserForm, User_},
     Crud,
     ListingType,
     SortType,
similarity index 89%
rename from lemmy_db/src/category.rs
rename to lemmy_db/src/source/category.rs
index 36beb9ff638b1a2070d9af70ecb7f4a2c2c6c6d3..95b65dc823ea31dc51f0bd8e6aafdb3193948565 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,
@@ -48,7 +48,7 @@ impl Category {
 
 #[cfg(test)]
 mod tests {
-  use crate::{category::Category, tests::establish_unpooled_connection};
+  use crate::{source::category::Category, tests::establish_unpooled_connection};
 
   #[test]
   fn test_crud() {
similarity index 98%
rename from lemmy_db/src/comment.rs
rename to lemmy_db/src/source/comment.rs
index f54ddd10f203def7fc3820ec4887a1196a379edb..dd4fb39dee27f9b5fce1adeaa789fe1dca8e1334 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,
 }
 
@@ -260,11 +260,8 @@ impl Saveable<CommentSavedForm> for CommentSaved {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    post::*,
+    source::{comment::*, community::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     Crud,
     ListingType,
     SortType,
similarity index 89%
rename from lemmy_db/src/community.rs
rename to lemmy_db/src/source/community.rs
index be40da349246692b0e56714294e2fb9fa7e6e361..ea7a028e1f863e2831edea344b1cdb0d11eee31b 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::{schema::community::columns::*, source::community::Community, 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)
   }
 
@@ -349,7 +415,12 @@ impl Followable<CommunityFollowerForm> for CommunityFollower {
 
 #[cfg(test)]
 mod tests {
-  use crate::{community::*, tests::establish_unpooled_connection, user::*, ListingType, SortType};
+  use crate::{
+    source::{community::*, user::*},
+    tests::establish_unpooled_connection,
+    ListingType,
+    SortType,
+  };
 
   #[test]
   fn test_crud() {
diff --git a/lemmy_db/src/source/mod.rs b/lemmy_db/src/source/mod.rs
new file mode 100644 (file)
index 0000000..2247cd8
--- /dev/null
@@ -0,0 +1,11 @@
+pub mod activity;
+pub mod category;
+pub mod comment;
+pub mod community;
+pub mod moderator;
+pub mod password_reset_request;
+pub mod post;
+pub mod private_message;
+pub mod site;
+pub mod user;
+pub mod user_mention;
similarity index 99%
rename from lemmy_db/src/moderator.rs
rename to lemmy_db/src/source/moderator.rs
index c0c0ff1067b3858153261f2edd2e08d97dd014ba..1be3e31b8f677bce0476e0a4fef0820ac13ce4b0 100644 (file)
@@ -392,12 +392,8 @@ impl Crud<ModAddForm> for ModAdd {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    moderator::*,
-    post::*,
+    source::{comment::*, community::*, moderator::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
similarity index 98%
rename from lemmy_db/src/password_reset_request.rs
rename to lemmy_db/src/source/password_reset_request.rs
index 8ae18cbd4fd18e796be459fa72ae175a31aa6d20..0cf0169f0ad4a18c79afd3d475d3ad5d5b0d428f 100644 (file)
@@ -80,7 +80,7 @@ impl PasswordResetRequest {
 mod tests {
   use super::super::user::*;
   use crate::{
-    password_reset_request::PasswordResetRequest,
+    source::password_reset_request::PasswordResetRequest,
     tests::establish_unpooled_connection,
     Crud,
     ListingType,
similarity index 98%
rename from lemmy_db/src/post.rs
rename to lemmy_db/src/source/post.rs
index 530f475b4c35b68299db7162b5a7052cb11402d0..b584798e87a98cc884abdb0cbe15bca2b166e44a 100644 (file)
@@ -8,9 +8,10 @@ use crate::{
   Saveable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 use url::{ParseError, Url};
 
-#[derive(Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
 #[table_name = "post"]
 pub struct Post {
   pub id: i32,
@@ -331,10 +332,8 @@ impl Readable<PostReadForm> for PostRead {
 #[cfg(test)]
 mod tests {
   use crate::{
-    community::*,
-    post::*,
+    source::{community::*, post::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
similarity index 99%
rename from lemmy_db/src/private_message.rs
rename to lemmy_db/src/source/private_message.rs
index 0e1aef10841fc580385f64f8ea44584ffa7ea0b4..47bb78fbb7399ea8696ff9ab2fd2a5d236379d86 100644 (file)
@@ -138,9 +138,8 @@ impl PrivateMessage {
 #[cfg(test)]
 mod tests {
   use crate::{
-    private_message::*,
+    source::{private_message::*, user::*},
     tests::establish_unpooled_connection,
-    user::*,
     ListingType,
     SortType,
   };
similarity index 95%
rename from lemmy_db/src/site.rs
rename to lemmy_db/src/source/site.rs
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,
similarity index 85%
rename from lemmy_db/src/user.rs
rename to lemmy_db/src/source/user.rs
index d8e833e6e5bdb2540e7d5d3777c821446edaa10e..5fdb56bbc6788528856aaed5ea280002f0ce5be4 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::*, source::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 {
@@ -213,7 +275,7 @@ impl User_ {
 
 #[cfg(test)]
 mod tests {
-  use crate::{tests::establish_unpooled_connection, user::*, ListingType, SortType};
+  use crate::{source::user::*, tests::establish_unpooled_connection, ListingType, SortType};
 
   #[test]
   fn test_crud() {
@@ -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();
similarity index 98%
rename from lemmy_db/src/user_mention.rs
rename to lemmy_db/src/source/user_mention.rs
index 68f566332dd8969f30c7fb141dc24eb10644bbed..7ad965218cc7d4b4e201ceb6f6b97425094c8ef5 100644 (file)
@@ -73,12 +73,8 @@ impl UserMention {
 #[cfg(test)]
 mod tests {
   use crate::{
-    comment::*,
-    community::*,
-    post::*,
+    source::{comment::*, community::*, post::*, user::*, user_mention::*},
     tests::establish_unpooled_connection,
-    user::*,
-    user_mention::*,
     ListingType,
     SortType,
   };
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..64adae3
--- /dev/null
@@ -0,0 +1,58 @@
+use crate::{
+  schema::{community, community_follower, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityFollowerView {
+  pub community: CommunitySafe,
+  pub follower: UserSafe,
+}
+
+type CommunityFollowerViewTuple = (CommunitySafe, 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::<CommunityFollowerViewTuple>(conn)?;
+
+    Ok(Self::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::<CommunityFollowerViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityFollowerView {
+  type DbTuple = CommunityFollowerViewTuple;
+  fn to_vec(users: Vec<Self::DbTuple>) -> Vec<Self> {
+    users
+      .iter()
+      .map(|a| Self {
+        community: a.0.to_owned(),
+        follower: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
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..c98f072
--- /dev/null
@@ -0,0 +1,58 @@
+use crate::{
+  schema::{community, community_moderator, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct CommunityModeratorView {
+  pub community: CommunitySafe,
+  pub moderator: UserSafe,
+}
+
+type CommunityModeratorViewTuple = (CommunitySafe, 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::<CommunityModeratorViewTuple>(conn)?;
+
+    Ok(Self::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::<CommunityModeratorViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityModeratorView {
+  type DbTuple = CommunityModeratorViewTuple;
+  fn to_vec(community_moderators: Vec<Self::DbTuple>) -> Vec<Self> {
+    community_moderators
+      .iter()
+      .map(|a| Self {
+        community: a.0.to_owned(),
+        moderator: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
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..3358f01
--- /dev/null
@@ -0,0 +1,35 @@
+use crate::{
+  schema::{community, community_user_ban, user_},
+  source::{
+    community::{Community, CommunitySafe},
+    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..d4518f7
--- /dev/null
@@ -0,0 +1,298 @@
+use crate::{
+  aggregates::community_aggregates::CommunityAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{category, community, community_aggregates, community_follower, user_},
+  source::{
+    category::Category,
+    community::{Community, CommunityFollower, CommunitySafe},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  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,
+}
+
+type CommunityViewTuple = (
+  CommunitySafe,
+  UserSafe,
+  Category,
+  CommunityAggregates,
+  Option<CommunityFollower>,
+);
+
+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, follower) = 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::<CommunityViewTuple>(conn)?;
+
+    Ok(CommunityView {
+      community,
+      creator,
+      category,
+      subscribed: follower.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::<CommunityViewTuple>(self.conn)?;
+
+    Ok(CommunityView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommunityView {
+  type DbTuple = CommunityViewTuple;
+  fn to_vec(communities: Vec<Self::DbTuple>) -> Vec<Self> {
+    communities
+      .iter()
+      .map(|a| Self {
+        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<Self>>()
+  }
+}
diff --git a/lemmy_db/src/views/mod.rs b/lemmy_db/src/views/mod.rs
new file mode 100644 (file)
index 0000000..465e5cf
--- /dev/null
@@ -0,0 +1,14 @@
+pub mod community_follower_view;
+pub mod community_moderator_view;
+pub mod community_user_ban_view;
+pub mod community_view;
+pub mod post_view;
+pub mod site_view;
+pub mod user_view;
+
+pub(crate) trait ViewToVec {
+  type DbTuple;
+  fn to_vec(tuple: Vec<Self::DbTuple>) -> Vec<Self>
+  where
+    Self: Sized;
+}
diff --git a/lemmy_db/src/views/post_view.rs b/lemmy_db/src/views/post_view.rs
new file mode 100644 (file)
index 0000000..4888f9c
--- /dev/null
@@ -0,0 +1,818 @@
+use crate::{
+  aggregates::post_aggregates::PostAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    post_aggregates,
+    post_like,
+    post_read,
+    post_saved,
+    user_,
+  },
+  source::{
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::{Post, PostRead, PostSaved},
+    user::{UserSafe, User_},
+  },
+  views::ViewToVec,
+  ListingType,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct PostView {
+  pub post: Post,
+  pub creator: UserSafe,
+  pub community: CommunitySafe,
+  pub counts: PostAggregates,
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub saved: bool,                         // Left join to PostSaved
+  pub read: bool,                          // Left join to PostRead
+  pub my_vote: Option<i16>,                // Left join to PostLike
+}
+
+type PostViewTuple = (
+  Post,
+  UserSafe,
+  CommunitySafe,
+  Option<CommunityUserBan>,
+  PostAggregates,
+  Option<CommunityFollower>,
+  Option<PostSaved>,
+  Option<PostRead>,
+  Option<i16>,
+);
+
+impl PostView {
+  pub fn read(conn: &PgConnection, post_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 (
+      post,
+      creator,
+      community,
+      creator_banned_from_community,
+      counts,
+      follower,
+      saved,
+      read,
+      my_vote,
+    ) = post::table
+      .find(post_id)
+      .inner_join(user_::table)
+      .inner_join(community::table)
+      .left_join(
+        community_user_ban::table.on(
+          post::community_id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(community::creator_id)),
+        ),
+      )
+      .inner_join(post_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_saved::table.on(
+          post::id
+            .eq(post_saved::post_id)
+            .and(post_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_read::table.on(
+          post::id
+            .eq(post_read::post_id)
+            .and(post_read::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_like::table.on(
+          post::id
+            .eq(post_like::post_id)
+            .and(post_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        post::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        community_user_ban::all_columns.nullable(),
+        post_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+        post_saved::all_columns.nullable(),
+        post_read::all_columns.nullable(),
+        post_like::score.nullable(),
+      ))
+      .first::<PostViewTuple>(conn)?;
+
+    Ok(PostView {
+      post,
+      creator,
+      community,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      counts,
+      subscribed: follower.is_some(),
+      saved: saved.is_some(),
+      read: read.is_some(),
+      my_vote,
+    })
+  }
+}
+
+mod join_types {
+  use crate::schema::{
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    post_aggregates,
+    post_like,
+    post_read,
+    post_saved,
+    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 BoxedPostJoin<'a> = BoxedSelectStatement<
+    'a,
+    (
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Integer,
+        Integer,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Bool,
+        Bool,
+        Nullable<Text>,
+        Nullable<Text>,
+        Nullable<Text>,
+        Nullable<Text>,
+        Text,
+        Bool,
+      ),
+      (
+        Integer,
+        Text,
+        Nullable<Text>,
+        Nullable<Text>,
+        Bool,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Nullable<Text>,
+        Text,
+        Nullable<Text>,
+        Bool,
+        Nullable<Text>,
+        Bool,
+      ),
+      (
+        Integer,
+        Text,
+        Text,
+        Nullable<Text>,
+        Integer,
+        Integer,
+        Bool,
+        Timestamp,
+        Nullable<Timestamp>,
+        Bool,
+        Bool,
+        Text,
+        Bool,
+        Nullable<Text>,
+        Nullable<Text>,
+      ),
+      Nullable<(Integer, Integer, Integer, Timestamp)>,
+      (Integer, Integer, BigInt, BigInt, BigInt, BigInt, Timestamp),
+      Nullable<(Integer, Integer, Integer, Timestamp, Nullable<Bool>)>,
+      Nullable<(Integer, Integer, Integer, Timestamp)>,
+      Nullable<(Integer, Integer, Integer, Timestamp)>,
+      Nullable<SmallInt>,
+    ),
+    JoinOn<
+      Join<
+        JoinOn<
+          Join<
+            JoinOn<
+              Join<
+                JoinOn<
+                  Join<
+                    JoinOn<
+                      Join<
+                        JoinOn<
+                          Join<
+                            JoinOn<
+                              Join<
+                                JoinOn<
+                                  Join<post::table, user_::table, Inner>,
+                                  diesel::expression::operators::Eq<
+                                    diesel::expression::nullable::Nullable<
+                                      post::columns::creator_id,
+                                    >,
+                                    diesel::expression::nullable::Nullable<user_::columns::id>,
+                                  >,
+                                >,
+                                community::table,
+                                Inner,
+                              >,
+                              diesel::expression::operators::Eq<
+                                diesel::expression::nullable::Nullable<post::columns::community_id>,
+                                diesel::expression::nullable::Nullable<community::columns::id>,
+                              >,
+                            >,
+                            community_user_ban::table,
+                            LeftOuter,
+                          >,
+                          diesel::expression::operators::And<
+                            diesel::expression::operators::Eq<
+                              post::columns::community_id,
+                              community_user_ban::columns::community_id,
+                            >,
+                            diesel::expression::operators::Eq<
+                              community_user_ban::columns::user_id,
+                              community::columns::creator_id,
+                            >,
+                          >,
+                        >,
+                        post_aggregates::table,
+                        Inner,
+                      >,
+                      diesel::expression::operators::Eq<
+                        diesel::expression::nullable::Nullable<post_aggregates::columns::post_id>,
+                        diesel::expression::nullable::Nullable<post::columns::id>,
+                      >,
+                    >,
+                    community_follower::table,
+                    LeftOuter,
+                  >,
+                  diesel::expression::operators::And<
+                    diesel::expression::operators::Eq<
+                      post::columns::community_id,
+                      community_follower::columns::community_id,
+                    >,
+                    diesel::expression::operators::Eq<
+                      community_follower::columns::user_id,
+                      diesel::expression::bound::Bound<Integer, i32>,
+                    >,
+                  >,
+                >,
+                post_saved::table,
+                LeftOuter,
+              >,
+              diesel::expression::operators::And<
+                diesel::expression::operators::Eq<post::columns::id, post_saved::columns::post_id>,
+                diesel::expression::operators::Eq<
+                  post_saved::columns::user_id,
+                  diesel::expression::bound::Bound<Integer, i32>,
+                >,
+              >,
+            >,
+            post_read::table,
+            LeftOuter,
+          >,
+          diesel::expression::operators::And<
+            diesel::expression::operators::Eq<post::columns::id, post_read::columns::post_id>,
+            diesel::expression::operators::Eq<
+              post_read::columns::user_id,
+              diesel::expression::bound::Bound<Integer, i32>,
+            >,
+          >,
+        >,
+        post_like::table,
+        LeftOuter,
+      >,
+      diesel::expression::operators::And<
+        diesel::expression::operators::Eq<post::columns::id, post_like::columns::post_id>,
+        diesel::expression::operators::Eq<
+          post_like::columns::user_id,
+          diesel::expression::bound::Bound<Integer, i32>,
+        >,
+      >,
+    >,
+    Pg,
+  >;
+}
+
+pub struct PostQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  query: join_types::BoxedPostJoin<'a>,
+  listing_type: &'a ListingType,
+  sort: &'a SortType,
+  for_creator_id: Option<i32>,
+  for_community_id: Option<i32>,
+  for_community_name: Option<String>,
+  search_term: Option<String>,
+  url_search: Option<String>,
+  show_nsfw: bool,
+  saved_only: bool,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> PostQueryBuilder<'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 = post::table
+      .inner_join(user_::table)
+      .inner_join(community::table)
+      .left_join(
+        community_user_ban::table.on(
+          post::community_id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(community::creator_id)),
+        ),
+      )
+      .inner_join(post_aggregates::table)
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_saved::table.on(
+          post::id
+            .eq(post_saved::post_id)
+            .and(post_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_read::table.on(
+          post::id
+            .eq(post_read::post_id)
+            .and(post_read::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        post_like::table.on(
+          post::id
+            .eq(post_like::post_id)
+            .and(post_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        post::all_columns,
+        User_::safe_columns_tuple(),
+        Community::safe_columns_tuple(),
+        community_user_ban::all_columns.nullable(),
+        post_aggregates::all_columns,
+        community_follower::all_columns.nullable(),
+        post_saved::all_columns.nullable(),
+        post_read::all_columns.nullable(),
+        post_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    PostQueryBuilder {
+      conn,
+      query,
+      listing_type: &ListingType::All,
+      sort: &SortType::Hot,
+      for_creator_id: None,
+      for_community_id: None,
+      for_community_name: None,
+      search_term: None,
+      url_search: None,
+      show_nsfw: true,
+      saved_only: false,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn listing_type(mut self, listing_type: &'a ListingType) -> Self {
+    self.listing_type = listing_type;
+    self
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
+    self.for_community_id = for_community_id.get_optional();
+    self
+  }
+
+  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
+    self.for_community_name = for_community_name.get_optional();
+    self
+  }
+
+  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
+    self.for_creator_id = for_creator_id.get_optional();
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn url_search<T: MaybeOptional<String>>(mut self, url_search: T) -> Self {
+    self.url_search = url_search.get_optional();
+    self
+  }
+
+  pub fn show_nsfw(mut self, show_nsfw: bool) -> Self {
+    self.show_nsfw = show_nsfw;
+    self
+  }
+
+  pub fn saved_only(mut self, saved_only: bool) -> Self {
+    self.saved_only = saved_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<PostView>, Error> {
+    use diesel::dsl::*;
+
+    let mut query = self.query;
+
+    query = match self.listing_type {
+      ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
+      ListingType::Local => query.filter(community::local.eq(true)),
+      _ => query,
+    };
+
+    if let Some(for_community_id) = self.for_community_id {
+      query = query
+        .filter(post::community_id.eq(for_community_id))
+        .then_order_by(post::stickied.desc());
+    }
+
+    if let Some(for_community_name) = self.for_community_name {
+      query = query
+        .filter(community::name.eq(for_community_name))
+        .filter(community::local.eq(true))
+        .then_order_by(post::stickied.desc());
+    }
+
+    if let Some(url_search) = self.url_search {
+      query = query.filter(post::url.eq(url_search));
+    }
+
+    if let Some(search_term) = self.search_term {
+      let searcher = fuzzy_search(&search_term);
+      query = query.filter(
+        post::name
+          .ilike(searcher.to_owned())
+          .or(post::body.ilike(searcher)),
+      );
+    }
+
+    query = match self.sort {
+      SortType::Active => query
+        .then_order_by(
+          hot_rank(post_aggregates::score, post_aggregates::newest_comment_time).desc(),
+        )
+        .then_order_by(post::published.desc()),
+      SortType::Hot => query
+        .then_order_by(hot_rank(post_aggregates::score, post::published).desc())
+        .then_order_by(post::published.desc()),
+      SortType::New => query.then_order_by(post::published.desc()),
+      SortType::TopAll => query.then_order_by(post_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(post::published.gt(now - 1.years()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(post::published.gt(now - 1.months()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(post::published.gt(now - 1.weeks()))
+        .then_order_by(post_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(post::published.gt(now - 1.days()))
+        .then_order_by(post_aggregates::score.desc()),
+    };
+
+    // If its for a specific user, show the removed / deleted
+    if let Some(for_creator_id) = self.for_creator_id {
+      query = query.filter(post::creator_id.eq(for_creator_id));
+    }
+
+    if !self.show_nsfw {
+      query = query
+        .filter(post::nsfw.eq(false))
+        .filter(community::nsfw.eq(false));
+    };
+
+    // TODO  These two might be wrong
+    if self.saved_only {
+      query = query.filter(post_saved::id.is_not_null());
+    };
+
+    if self.unread_only {
+      query = query.filter(post_read::id.is_not_null());
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .filter(post::removed.eq(false))
+      .filter(post::deleted.eq(false))
+      .filter(community::removed.eq(false))
+      .filter(community::deleted.eq(false))
+      .load::<PostViewTuple>(self.conn)?;
+
+    Ok(PostView::to_vec(res))
+  }
+}
+
+impl ViewToVec for PostView {
+  type DbTuple = PostViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        post: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        community: a.2.to_owned(),
+        creator_banned_from_community: a.3.is_some(),
+        counts: a.4.to_owned(),
+        subscribed: a.5.is_some(),
+        saved: a.6.is_some(),
+        read: a.7.is_some(),
+        my_vote: a.8,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    aggregates::post_aggregates::PostAggregates,
+    source::{community::*, post::*, user::*},
+    tests::establish_unpooled_connection,
+    views::post_view::{PostQueryBuilder, PostView},
+    Crud,
+    Likeable,
+    *,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let user_name = "tegan".to_string();
+    let community_name = "test_community_3".to_string();
+    let post_name = "test post 3".to_string();
+
+    let new_user = UserForm {
+      name: user_name.to_owned(),
+      preferred_username: None,
+      password_encrypted: "nope".into(),
+      email: None,
+      matrix_user_id: None,
+      avatar: None,
+      banner: None,
+      published: None,
+      updated: None,
+      admin: false,
+      banned: Some(false),
+      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: community_name.to_owned(),
+      title: "nada".to_owned(),
+      description: None,
+      creator_id: inserted_user.id,
+      category_id: 1,
+      removed: None,
+      deleted: None,
+      updated: None,
+      nsfw: false,
+      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: post_name.to_owned(),
+      url: None,
+      body: None,
+      creator_id: inserted_user.id,
+      community_id: inserted_community.id,
+      removed: None,
+      deleted: None,
+      locked: None,
+      stickied: None,
+      updated: None,
+      nsfw: false,
+      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_form = PostLikeForm {
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
+
+    let expected_post_like = PostLike {
+      id: inserted_post_like.id,
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      published: inserted_post_like.published,
+      score: 1,
+    };
+
+    let read_post_listings_with_user = PostQueryBuilder::create(&conn, Some(inserted_user.id))
+      .listing_type(&ListingType::Community)
+      .sort(&SortType::New)
+      .for_community_id(inserted_community.id)
+      .list()
+      .unwrap();
+
+    let read_post_listings_no_user = PostQueryBuilder::create(&conn, None)
+      .listing_type(&ListingType::Community)
+      .sort(&SortType::New)
+      .for_community_id(inserted_community.id)
+      .list()
+      .unwrap();
+
+    let read_post_listing_no_user = PostView::read(&conn, inserted_post.id, None).unwrap();
+    let read_post_listing_with_user =
+      PostView::read(&conn, inserted_post.id, Some(inserted_user.id)).unwrap();
+
+    // the non user version
+    let expected_post_listing_no_user = PostView {
+      post: Post {
+        id: inserted_post.id,
+        name: post_name.to_owned(),
+        creator_id: inserted_user.id,
+        url: None,
+        body: None,
+        published: inserted_post.published,
+        updated: None,
+        community_id: inserted_community.id,
+        removed: false,
+        deleted: false,
+        locked: false,
+        stickied: false,
+        nsfw: false,
+        embed_title: None,
+        embed_description: None,
+        embed_html: None,
+        thumbnail_url: None,
+        ap_id: inserted_post.ap_id.to_owned(),
+        local: true,
+      },
+      my_vote: None,
+      creator: UserSafe {
+        id: inserted_user.id,
+        name: user_name.to_owned(),
+        preferred_username: None,
+        published: inserted_user.published,
+        avatar: None,
+        actor_id: inserted_user.actor_id.to_owned(),
+        local: true,
+        banned: false,
+        deleted: false,
+        bio: None,
+        banner: None,
+        admin: false,
+        updated: None,
+        matrix_user_id: None,
+      },
+      creator_banned_from_community: false,
+      community: CommunitySafe {
+        id: inserted_community.id,
+        name: community_name.to_owned(),
+        icon: None,
+        removed: false,
+        deleted: false,
+        nsfw: false,
+        actor_id: inserted_community.actor_id.to_owned(),
+        local: true,
+        title: "nada".to_owned(),
+        description: None,
+        creator_id: inserted_user.id,
+        category_id: 1,
+        updated: None,
+        banner: None,
+        published: inserted_community.published,
+      },
+      counts: PostAggregates {
+        id: inserted_post.id, // TODO this might fail
+        post_id: inserted_post.id,
+        comments: 0,
+        score: 1,
+        upvotes: 1,
+        downvotes: 0,
+        newest_comment_time: inserted_post.published,
+      },
+      subscribed: false,
+      read: false,
+      saved: false,
+    };
+
+    // TODO More needs to be added here
+    let mut expected_post_listing_with_user = expected_post_listing_no_user.to_owned();
+    expected_post_listing_with_user.my_vote = Some(1);
+
+    let like_removed = PostLike::remove(&conn, inserted_user.id, inserted_post.id).unwrap();
+    let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
+    Community::delete(&conn, inserted_community.id).unwrap();
+    User_::delete(&conn, inserted_user.id).unwrap();
+
+    // The with user
+    assert_eq!(
+      expected_post_listing_with_user,
+      read_post_listings_with_user[0]
+    );
+    assert_eq!(expected_post_listing_with_user, read_post_listing_with_user);
+    assert_eq!(1, read_post_listings_with_user.len());
+
+    // Without the user
+    assert_eq!(expected_post_listing_no_user, read_post_listings_no_user[0]);
+    assert_eq!(expected_post_listing_no_user, read_post_listing_no_user);
+    assert_eq!(1, read_post_listings_no_user.len());
+
+    // assert_eq!(expected_post, inserted_post);
+    // assert_eq!(expected_post, updated_post);
+    assert_eq!(expected_post_like, inserted_post_like);
+    assert_eq!(1, like_removed);
+    assert_eq!(1, num_deleted);
+  }
+}
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..d10702f
--- /dev/null
@@ -0,0 +1,27 @@
+use crate::{
+  schema::{site, user_},
+  source::{
+    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..4d4e78c
--- /dev/null
@@ -0,0 +1,211 @@
+use crate::{
+  aggregates::user_aggregates::UserAggregates,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{user_, user_aggregates},
+  source::user::{UserSafe, User_},
+  views::ViewToVec,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewSafe {
+  pub user: UserSafe,
+  pub counts: UserAggregates,
+}
+
+type UserViewSafeTuple = (UserSafe, UserAggregates);
+
+#[derive(Debug, Serialize, Clone)]
+pub struct UserViewDangerous {
+  pub user: User_,
+  pub counts: UserAggregates,
+}
+
+type UserViewDangerousTuple = (User_, 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::<UserViewDangerousTuple>(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::<UserViewSafeTuple>(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::<UserViewSafeTuple>(conn)?;
+
+    Ok(Self::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::<UserViewSafeTuple>(conn)?;
+
+    Ok(Self::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::<UserViewSafeTuple>(self.conn)?;
+
+    Ok(UserViewSafe::to_vec(res))
+  }
+}
+
+impl ViewToVec for UserViewSafe {
+  type DbTuple = UserViewSafeTuple;
+  fn to_vec(users: Vec<Self::DbTuple>) -> Vec<Self> {
+    users
+      .iter()
+      .map(|a| Self {
+        user: a.0.to_owned(),
+        counts: a.1.to_owned(),
+      })
+      .collect::<Vec<Self>>()
+  }
+}
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 5d2e42733e193985105ba8bf65fc89b5c98cddc8..3a2e28d94ce0b175a08f5f60741e5e94b5796eed 100644 (file)
@@ -7,10 +7,12 @@ pub mod websocket;
 
 use diesel::PgConnection;
 use lemmy_db::{
-  comment::Comment,
-  post::Post,
-  user::User_,
-  user_mention::{UserMention, UserMentionForm},
+  source::{
+    comment::Comment,
+    post::Post,
+    user::User_,
+    user_mention::{UserMention, UserMentionForm},
+  },
   Crud,
   DbPool,
 };
index 331c2dca45e944636d101b65697db5920af8176c..49ae14c2e79523bac60ace4dfb59bc71f60e987e 100644 (file)
@@ -1,8 +1,11 @@
 use lemmy_db::{
   comment_view::CommentView,
-  community_view::{CommunityModeratorView, CommunityView},
   post_report::PostReportView,
-  post_view::PostView,
+  views::{
+    community_moderator_view::CommunityModeratorView,
+    community_view::CommunityView,
+    post_view::PostView,
+  },
 };
 use serde::{Deserialize, Serialize};
 
@@ -18,7 +21,7 @@ pub struct CreatePost {
 
 #[derive(Serialize, Clone)]
 pub struct PostResponse {
-  pub post: PostView,
+  pub post_view: PostView,
 }
 
 #[derive(Deserialize)]
@@ -29,7 +32,7 @@ pub struct GetPost {
 
 #[derive(Serialize)]
 pub struct GetPostResponse {
-  pub post: PostView,
+  pub post_view: PostView,
   pub comments: Vec<CommentView>,
   pub community: CommunityView,
   pub moderators: Vec<CommunityModeratorView>,
index 3f185928b4b2c1fb61322fb8a74da9a96b03f6e4..002c3ace2c5c5ddf5941d04e291a2ec9db07cb2a 100644 (file)
@@ -1,12 +1,14 @@
 use lemmy_db::{
-  category::*,
+  aggregates::site_aggregates::SiteAggregates,
   comment_view::*,
-  community_view::*,
   moderator_views::*,
-  post_view::*,
-  site_view::*,
-  user::*,
-  user_view::*,
+  source::{category::*, user::*},
+  views::{
+    community_view::CommunityView,
+    post_view::PostView,
+    site_view::SiteView,
+    user_view::UserViewSafe,
+  },
 };
 use serde::{Deserialize, Serialize};
 
@@ -36,7 +38,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 +91,7 @@ pub struct GetSite {
   pub auth: Option<String>,
 }
 
+// TODO combine siteresponse and getsiteresponse
 #[derive(Serialize, Clone)]
 pub struct SiteResponse {
   pub site: SiteView,
@@ -96,9 +99,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..eb891c2f7e35882bbdd07fc6824f0daf1e78f6bf 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,
+    post_view::PostView,
+    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,
 }
 
index 0be54c33f8fb2241bf3ac41811aad652195d8699..86025ade78aaeb4dff37a2d232873a68be4ebb25 100644 (file)
@@ -368,22 +368,28 @@ impl ChatServer {
   pub fn send_post(
     &self,
     user_operation: &UserOperation,
-    post: &PostResponse,
+    post_res: &PostResponse,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError> {
-    let community_id = post.post.community_id;
+    let community_id = post_res.post_view.community.id;
 
     // Don't send my data with it
-    let mut post_sent = post.clone();
-    post_sent.post.my_vote = None;
-    post_sent.post.user_id = None;
+    // TODO no idea what to do here
+    // let mut post_sent = post_res.clone();
+    // post_sent.post.my_vote = None;
+    // post_sent.post.user_id = None;
 
     // Send it to /c/all and that community
-    self.send_community_room_message(user_operation, &post_sent, 0, websocket_id)?;
-    self.send_community_room_message(user_operation, &post_sent, community_id, websocket_id)?;
+    self.send_community_room_message(user_operation, &post_res, 0, websocket_id)?;
+    self.send_community_room_message(user_operation, &post_res, community_id, websocket_id)?;
 
     // Send it to the post room
-    self.send_post_room_message(user_operation, &post_sent, post.post.id, websocket_id)?;
+    self.send_post_room_message(
+      user_operation,
+      &post_res,
+      post_res.post_view.post.id,
+      websocket_id,
+    )?;
 
     Ok(())
   }
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..b957234
--- /dev/null
@@ -0,0 +1,96 @@
+-- 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_
+for each row
+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..7b4c83a
--- /dev/null
@@ -0,0 +1,178 @@
+-- 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 = OLD.creator_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
+    -- 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 = OLD.creator_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..fc0ffd2
--- /dev/null
@@ -0,0 +1,11 @@
+-- community aggregates
+drop table community_aggregates;
+drop trigger community_aggregates_community on community;
+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_community,
+  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..18a6229
--- /dev/null
@@ -0,0 +1,136 @@
+-- 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 default 0,
+  posts bigint not null default 0,
+  comments bigint not null default 0,
+  unique (community_id)
+);
+
+insert into community_aggregates (community_id, subscribers, posts, comments)
+  select 
+    c.id,
+    coalesce(cf.subs, 0) as subscribers,
+    coalesce(cd.posts, 0) as posts,
+    coalesce(cd.comments, 0) 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
+
+-- initial community add
+create function community_aggregates_community()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into community_aggregates (community_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from community_aggregates where community_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger community_aggregates_community
+after insert or delete on community
+for each row
+execute procedure community_aggregates_community();
+-- 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;
+
+    -- Update the counts if the post got deleted
+    update community_aggregates ca
+    set posts = coalesce(cd.posts, 0),
+    comments = coalesce(cd.comments, 0)
+    from ( 
+      select 
+      c.id,
+      count(distinct p.id) as posts,
+      count(distinct ct.id) as comments
+      from community c
+      left join post p on c.id = p.community_id
+      left join comment ct on p.id = ct.post_id
+      group by c.id
+    ) cd 
+    where ca.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 ca
+    set comments = comments + 1 from comment c, post p
+    where p.id = c.post_id 
+    and p.id = NEW.post_id 
+    and ca.community_id = p.community_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    update community_aggregates ca
+    set comments = comments - 1 from comment c, post p
+    where p.id = c.post_id 
+    and p.id = OLD.post_id 
+    and ca.community_id = p.community_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();
+
diff --git a/migrations/2020-12-10-152350_create_post_aggregates/down.sql b/migrations/2020-12-10-152350_create_post_aggregates/down.sql
new file mode 100644 (file)
index 0000000..113f26d
--- /dev/null
@@ -0,0 +1,9 @@
+-- post aggregates
+drop table post_aggregates;
+drop trigger post_aggregates_post on post;
+drop trigger post_aggregates_comment_count on comment;
+drop trigger post_aggregates_score on post_like;
+drop function 
+  post_aggregates_post,
+  post_aggregates_comment_count,
+  post_aggregates_score;
diff --git a/migrations/2020-12-10-152350_create_post_aggregates/up.sql b/migrations/2020-12-10-152350_create_post_aggregates/up.sql
new file mode 100644 (file)
index 0000000..b3dc627
--- /dev/null
@@ -0,0 +1,113 @@
+-- Add post aggregates
+create table post_aggregates (
+  id serial primary key,
+  post_id int references post on update cascade on delete cascade not null,
+  comments bigint not null default 0,
+  score bigint not null default 0,
+  upvotes bigint not null default 0,
+  downvotes bigint not null default 0,
+  newest_comment_time timestamp not null default now(),
+  unique (post_id)
+);
+
+insert into post_aggregates (post_id, comments, score, upvotes, downvotes, newest_comment_time)
+  select 
+    p.id,
+    coalesce(ct.comments, 0::bigint) as comments,
+    coalesce(pl.score, 0::bigint) as score,
+    coalesce(pl.upvotes, 0::bigint) as upvotes,
+    coalesce(pl.downvotes, 0::bigint) as downvotes,
+    greatest(ct.recent_comment_time, p.published) as newest_activity_time
+  from post p
+  left join ( 
+    select comment.post_id,
+    count(*) as comments,
+    max(comment.published) as recent_comment_time
+    from comment
+    group by comment.post_id
+  ) ct on ct.post_id = p.id
+  left join ( 
+    select post_like.post_id,
+    sum(post_like.score) as score,
+    sum(post_like.score) filter (where post_like.score = 1) as upvotes,
+    -sum(post_like.score) filter (where post_like.score = '-1'::integer) as downvotes
+    from post_like
+    group by post_like.post_id
+  ) pl on pl.post_id = p.id;
+
+-- Add community aggregate triggers
+
+-- initial post add
+create function post_aggregates_post()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into post_aggregates (post_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from post_aggregates where post_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_post
+after insert or delete on post
+for each row
+execute procedure post_aggregates_post();
+
+-- comment count
+create function post_aggregates_comment_count()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update post_aggregates pa
+    set comments = comments + 1,
+    newest_comment_time = NEW.published
+    where pa.post_id = NEW.post_id;
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to post because that post may not exist anymore
+    update post_aggregates pa
+    set comments = comments - 1
+    from post p
+    where pa.post_id = p.id
+    and pa.post_id = OLD.post_id;
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_comment_count
+after insert or delete on comment
+for each row
+execute procedure post_aggregates_comment_count();
+
+-- post score
+create function post_aggregates_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update post_aggregates pa
+    set score = score + NEW.score,
+    upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
+    downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
+    where pa.post_id = NEW.post_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to post because that post may not exist anymore
+    update post_aggregates pa
+    set score = score - OLD.score,
+    upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
+    downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
+    from post p
+    where pa.post_id = p.id
+    and pa.post_id = OLD.post_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger post_aggregates_score
+after insert or delete on post_like
+for each row
+execute procedure post_aggregates_score();
index c41f5bd966ae53c9a0bf90ecc7a6c3f0f4c43edf..7a749e9b4a54c4aec610e7dfed46ffc7ff5afea0 100644 (file)
@@ -4,12 +4,14 @@ use diesel::{
   *,
 };
 use lemmy_db::{
-  comment::Comment,
-  community::{Community, CommunityForm},
   naive_now,
-  post::Post,
-  private_message::PrivateMessage,
-  user::{UserForm, User_},
+  source::{
+    comment::Comment,
+    community::{Community, CommunityForm},
+    post::Post,
+    private_message::PrivateMessage,
+    user::{UserForm, User_},
+  },
   Crud,
 };
 use lemmy_utils::{
index fc4a313724eca25014441bd82f921efe3dcfc73b..887faa88dc032d20301bd4ecf19161578af4aa80 100644 (file)
@@ -5,11 +5,12 @@ use diesel::PgConnection;
 use lemmy_api::claims::Claims;
 use lemmy_db::{
   comment_view::{ReplyQueryBuilder, ReplyView},
-  community::Community,
-  post_view::{PostQueryBuilder, PostView},
-  site_view::SiteView,
-  user::User_,
+  source::{community::Community, user::User_},
   user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  views::{
+    post_view::{PostQueryBuilder, PostView},
+    site_view::SiteView,
+  },
   ListingType,
   SortType,
 };
@@ -82,7 +83,7 @@ async fn get_feed_data(
 
   let listing_type_ = listing_type.clone();
   let posts = blocking(context.pool(), move |conn| {
-    PostQueryBuilder::create(&conn)
+    PostQueryBuilder::create(&conn, None)
       .listing_type(&listing_type_)
       .sort(&sort_type)
       .list()
@@ -96,13 +97,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);
   }
 
@@ -164,7 +165,7 @@ fn get_feed_user(
   let user = User_::find_by_username(&conn, &user_name)?;
   let user_url = user.get_profile_url(&Settings::get().hostname);
 
-  let posts = PostQueryBuilder::create(&conn)
+  let posts = PostQueryBuilder::create(&conn, None)
     .listing_type(&ListingType::All)
     .sort(sort_type)
     .for_creator_id(user.id)
@@ -175,7 +176,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);
 
@@ -190,7 +191,7 @@ fn get_feed_community(
   let site_view = SiteView::read(&conn)?;
   let community = Community::read_from_name(&conn, &community_name)?;
 
-  let posts = PostQueryBuilder::create(&conn)
+  let posts = PostQueryBuilder::create(&conn, None)
     .listing_type(&ListingType::All)
     .sort(sort_type)
     .for_community_id(community.id)
@@ -201,7 +202,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);
 
@@ -220,10 +221,9 @@ fn get_feed_front(
   let site_view = SiteView::read(&conn)?;
   let user_id = Claims::decode(&jwt)?.claims.id;
 
-  let posts = PostQueryBuilder::create(&conn)
+  let posts = PostQueryBuilder::create(&conn, Some(user_id))
     .listing_type(&ListingType::Subscribed)
     .sort(sort_type)
-    .my_user_id(user_id)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -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);
   }
 
@@ -349,17 +349,17 @@ fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
     let mut i = ItemBuilder::default();
     let mut dc_extension = DublinCoreExtensionBuilder::default();
 
-    i.title(p.name);
+    i.title(p.post.name);
 
-    dc_extension.creators(vec![p.creator_actor_id.to_owned()]);
+    dc_extension.creators(vec![p.creator.actor_id.to_owned()]);
 
-    let dt = DateTime::<Utc>::from_utc(p.published, Utc);
+    let dt = DateTime::<Utc>::from_utc(p.post.published, Utc);
     i.pub_date(dt.to_rfc2822());
 
     let post_url = format!(
       "{}/post/{}",
       Settings::get().get_protocol_and_hostname(),
-      p.id
+      p.post.id
     );
     i.comments(post_url.to_owned());
     let guid = GuidBuilder::default()
@@ -372,27 +372,27 @@ fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
     let community_url = format!(
       "{}/c/{}",
       Settings::get().get_protocol_and_hostname(),
-      p.community_name
+      p.community.name
     );
 
     // TODO: for category we should just put the name of the category, but then we would have
     //       to read each community from the db
 
-    if let Some(url) = p.url {
+    if let Some(url) = p.post.url {
       i.link(url);
     }
 
     // TODO add images
     let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
-    p.creator_actor_id,
-    p.creator_name,
+    p.creator.actor_id,
+    p.creator.name,
     community_url,
-    p.community_name,
-    p.score,
+    p.community.name,
+    p.counts.score,
     post_url,
-    p.number_of_comments);
+    p.counts.comments);
 
-    if let Some(body) = p.body {
+    if let Some(body) = p.post.body {
       let html = markdown_to_html(&body);
       description.push_str(&html);
     }
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,
     },
   };
 
index ba687abdf47507293da56d256634da52aa95be92..d59b4e38909aec88f2bb5605339e97d87db23c14 100644 (file)
@@ -1,6 +1,6 @@
 use actix_web::{error::ErrorBadRequest, web::Query, *};
 use anyhow::anyhow;
-use lemmy_db::{community::Community, user::User_};
+use lemmy_db::source::{community::Community, user::User_};
 use lemmy_structs::{blocking, WebFingerLink, WebFingerResponse};
 use lemmy_utils::{
   settings::Settings,
index 2a79dd4b527bfb2363e7f33412cd1abca018b6f5..a61c8ff6e02f5e23a3ee93ebf5cbe8b3afe3edf9 100644 (file)
@@ -29,8 +29,10 @@ use lemmy_apub::{
   },
 };
 use lemmy_db::{
-  community::{Community, CommunityForm},
-  user::{User_, *},
+  source::{
+    community::{Community, CommunityForm},
+    user::{User_, *},
+  },
   Crud,
   ListingType,
   SortType,