]> Untitled Git - lemmy.git/commitdiff
Merge branch 'main' into move_views_to_diesel
authorDessalines <tyhou13@gmx.com>
Wed, 16 Dec 2020 19:01:16 +0000 (14:01 -0500)
committerDessalines <tyhou13@gmx.com>
Wed, 16 Dec 2020 19:01:16 +0000 (14:01 -0500)
99 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/comment_aggregates.rs [new file with mode: 0644]
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 [deleted file]
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 94% 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 99% 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 69% similarity]
lemmy_db/src/source/user_mention.rs [moved from lemmy_db/src/user_mention.rs with 97% similarity]
lemmy_db/src/user_mention_view.rs [deleted file]
lemmy_db/src/user_view.rs [deleted file]
lemmy_db/src/views/comment_view.rs [new file with mode: 0644]
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_mention_view.rs [new file with mode: 0644]
lemmy_db/src/views/user_view.rs [new file with mode: 0644]
lemmy_structs/src/comment.rs
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]
migrations/2020-12-14-020038_create_comment_aggregates/down.sql [new file with mode: 0644]
migrations/2020-12-14-020038_create_comment_aggregates/up.sql [new file with mode: 0644]
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..b39444efd0a0e247be8b33851707053b99fe96b6 100644 (file)
@@ -10,13 +10,12 @@ 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::{
+    comment_view::{CommentQueryBuilder, CommentView},
+    site_view::SiteView,
+  },
   Crud,
   Likeable,
   ListingType,
@@ -138,7 +137,7 @@ impl Perform for CreateComment {
     .await??;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: data.form_id.to_owned(),
     };
@@ -175,10 +174,10 @@ impl Perform for EditComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the creator can edit
-    if user.id != orig_comment.creator_id {
+    if user.id != orig_comment.creator.id {
       return Err(APIError::err("no_comment_edit_allowed").into());
     }
 
@@ -198,7 +197,7 @@ impl Perform for EditComment {
     updated_comment.send_update(&user, context).await?;
 
     // Do the mentions / recipients
-    let post_id = orig_comment.post_id;
+    let post_id = orig_comment.post.id;
     let post = get_post(post_id, context.pool()).await?;
 
     let updated_comment_content = updated_comment.content.to_owned();
@@ -221,7 +220,7 @@ impl Perform for EditComment {
     .await??;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: data.form_id.to_owned(),
     };
@@ -258,10 +257,10 @@ impl Perform for DeleteComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the creator can delete
-    if user.id != orig_comment.creator_id {
+    if user.id != orig_comment.creator.id {
       return Err(APIError::err("no_comment_edit_allowed").into());
     }
 
@@ -292,7 +291,7 @@ impl Perform for DeleteComment {
     .await??;
 
     // Build the recipients
-    let post_id = comment_view.post_id;
+    let post_id = comment_view.post.id;
     let post = get_post(post_id, context.pool()).await?;
     let mentions = vec![];
     let recipient_ids = send_local_notifs(
@@ -306,7 +305,7 @@ impl Perform for DeleteComment {
     .await?;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: None,
     };
@@ -343,10 +342,10 @@ impl Perform for RemoveComment {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only a mod or admin can remove
-    is_mod_or_admin(context.pool(), user.id, orig_comment.community_id).await?;
+    is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
 
     // Do the remove
     let removed = data.removed;
@@ -387,7 +386,7 @@ impl Perform for RemoveComment {
     .await??;
 
     // Build the recipients
-    let post_id = comment_view.post_id;
+    let post_id = comment_view.post.id;
     let post = get_post(post_id, context.pool()).await?;
     let mentions = vec![];
     let recipient_ids = send_local_notifs(
@@ -401,7 +400,7 @@ impl Perform for RemoveComment {
     .await?;
 
     let mut res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids,
       form_id: None,
     };
@@ -438,23 +437,23 @@ impl Perform for MarkCommentAsRead {
     })
     .await??;
 
-    check_community_ban(user.id, orig_comment.community_id, context.pool()).await?;
+    check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
 
     // Verify that only the recipient can mark as read
     // Needs to fetch the parent comment / post to get the recipient
-    let parent_id = orig_comment.parent_id;
+    let parent_id = orig_comment.comment.parent_id;
     match parent_id {
       Some(pid) => {
         let parent_comment = blocking(context.pool(), move |conn| {
           CommentView::read(&conn, pid, None)
         })
         .await??;
-        if user.id != parent_comment.creator_id {
+        if user.id != parent_comment.creator.id {
           return Err(APIError::err("no_comment_edit_allowed").into());
         }
       }
       None => {
-        let parent_post_id = orig_comment.post_id;
+        let parent_post_id = orig_comment.post.id;
         let parent_post =
           blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
         if user.id != parent_post.creator_id {
@@ -483,7 +482,7 @@ impl Perform for MarkCommentAsRead {
     .await??;
 
     let res = CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids: Vec::new(),
       form_id: None,
     };
@@ -529,7 +528,7 @@ impl Perform for SaveComment {
     .await??;
 
     Ok(CommentResponse {
-      comment: comment_view,
+      comment_view,
       recipient_ids: Vec::new(),
       form_id: None,
     })
@@ -552,8 +551,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());
       }
     }
@@ -564,7 +563,7 @@ impl Perform for CreateCommentLike {
     })
     .await??;
 
-    let post_id = orig_comment.post_id;
+    let post_id = orig_comment.post.id;
     let post = get_post(post_id, context.pool()).await?;
     check_community_ban(user.id, post.community_id, context.pool()).await?;
 
@@ -630,7 +629,7 @@ impl Perform for CreateCommentLike {
     .await??;
 
     let mut res = CommentResponse {
-      comment: liked_comment,
+      comment_view: liked_comment,
       recipient_ids,
       form_id: None,
     };
@@ -673,8 +672,8 @@ impl Perform for GetComments {
       CommentQueryBuilder::create(conn)
         .listing_type(type_)
         .sort(&sort)
-        .for_community_id(community_id)
-        .for_community_name(community_name)
+        .community_id(community_id)
+        .community_name(community_name)
         .my_user_id(user_id)
         .page(page)
         .limit(limit)
@@ -714,17 +713,17 @@ impl Perform for CreateCommentReport {
 
     let user_id = user.id;
     let comment_id = data.comment_id;
-    let comment = blocking(context.pool(), move |conn| {
+    let comment_view = blocking(context.pool(), move |conn| {
       CommentView::read(&conn, comment_id, None)
     })
     .await??;
 
-    check_community_ban(user_id, comment.community_id, context.pool()).await?;
+    check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
 
     let report_form = CommentReportForm {
       creator_id: user_id,
       comment_id,
-      original_comment_text: comment.content,
+      original_comment_text: comment_view.comment.content,
       reason: data.reason.to_owned(),
     };
 
@@ -749,7 +748,7 @@ impl Perform for CreateCommentReport {
     context.chat_server().do_send(SendModRoomMessage {
       op: UserOperation::CreateCommentReport,
       response: report,
-      community_id: comment.community_id,
+      community_id: comment_view.community.id,
       websocket_id,
     });
 
index d7de0e6bd717a3bab6ad626599c846c08e5dfe26..04059a7c5b04ed9cbd5876f74ae6edce13bcdfe0 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::{
+    comment_view::CommentQueryBuilder,
+    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);
 
@@ -448,8 +440,8 @@ impl Perform for ListCommunities {
     let communities = blocking(context.pool(), move |conn| {
       CommunityQueryBuilder::create(conn)
         .sort(&sort)
-        .for_user(user_id)
         .show_nsfw(show_nsfw)
+        .my_user_id(user_id)
         .page(page)
         .limit(limit)
         .list()
@@ -520,12 +512,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 })
   }
 }
 
@@ -603,15 +593,15 @@ impl Perform for BanFromCommunity {
       // Diesel doesn't allow updates with joins, so this has to be a loop
       let comments = blocking(context.pool(), move |conn| {
         CommentQueryBuilder::create(conn)
-          .for_creator_id(banned_user_id)
-          .for_community_id(community_id)
+          .creator_id(banned_user_id)
+          .community_id(community_id)
           .limit(std::i64::MAX)
           .list()
       })
       .await??;
 
-      for comment in &comments {
-        let comment_id = comment.id;
+      for comment_view in &comments {
+        let comment_id = comment_view.comment.id;
         blocking(context.pool(), move |conn: &'_ _| {
           Comment::update_removed(conn, comment_id, remove_data)
         })
@@ -641,12 +631,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 +739,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 +770,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 +784,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 +829,7 @@ impl Perform for TransferCommunity {
 
     // Return the jwt
     Ok(GetCommunityResponse {
-      community: community_view,
+      community_view,
       moderators,
       online: 0,
     })
@@ -850,15 +842,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..d998e7d977dc2b53d41a374d66bcb83bd71e722b 100644 (file)
@@ -10,14 +10,16 @@ use crate::{
 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::{
+    comment_view::CommentQueryBuilder,
+    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,
@@ -180,20 +182,20 @@ impl Perform for GetPost {
     let id = data.id;
     let comments = blocking(context.pool(), move |conn| {
       CommentQueryBuilder::create(conn)
-        .for_post_id(id)
         .my_user_id(user_id)
+        .post_id(id)
         .limit(9999)
         .list()
     })
     .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,
@@ -250,8 +252,8 @@ impl Perform for GetPosts {
         .listing_type(&type_)
         .sort(&sort)
         .show_nsfw(show_nsfw)
-        .for_community_id(community_id)
-        .for_community_name(community_name)
+        .community_id(community_id)
+        .community_name(community_name)
         .my_user_id(user_id)
         .page(page)
         .limit(limit)
@@ -281,8 +283,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 +337,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 +430,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 +486,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 +553,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 +611,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 +673,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 +721,7 @@ impl Perform for SavePost {
     })
     .await??;
 
-    Ok(PostResponse { post: post_view })
+    Ok(PostResponse { post_view })
   }
 }
 
@@ -769,19 +771,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 +808,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..e8dfaca3221466cf2cee812fcfb99a303d5300b6 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::*,
-  comment_view::*,
-  community_view::*,
+  aggregates::site_aggregates::SiteAggregates,
   diesel_option_overwrite,
-  moderator::*,
   moderator_views::*,
   naive_now,
-  post_view::*,
-  site::*,
-  site_view::*,
-  user_view::*,
+  source::{category::*, moderator::*, site::*},
+  views::{
+    comment_view::CommentQueryBuilder,
+    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,
     })
   }
 }
@@ -362,10 +366,10 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
-            .search_term(q)
+            .community_id(community_id)
+            .community_name(community_name)
             .my_user_id(user_id)
+            .search_term(q)
             .page(page)
             .limit(limit)
             .list()
@@ -411,10 +415,10 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
-            .search_term(q)
+            .community_id(community_id)
+            .community_name(community_name)
             .my_user_id(user_id)
+            .search_term(q)
             .page(page)
             .limit(limit)
             .list()
@@ -466,8 +470,8 @@ impl Perform for Search {
           PostQueryBuilder::create(conn)
             .sort(&sort)
             .show_nsfw(true)
-            .for_community_id(community_id)
-            .for_community_name(community_name)
+            .community_id(community_id)
+            .community_name(community_name)
             .url_search(q)
             .page(page)
             .limit(limit)
@@ -531,15 +535,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 +555,7 @@ impl Perform for TransferSite {
       version: version::VERSION.to_string(),
       my_user: Some(user),
       federated_instances: linked_instances(context.pool()).await?,
+      counts,
     })
   }
 }
@@ -587,8 +594,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..17e3fac655756865d488f9f1b195b2cda55b5410 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::*,
-  user_mention_view::*,
-  user_view::*,
+  source::{
+    comment::*,
+    community::*,
+    moderator::*,
+    password_reset_request::*,
+    post::*,
+    private_message::*,
+    site::*,
+    user::*,
+    user_mention::*,
+  },
+  views::{
+    comment_view::CommentQueryBuilder,
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    post_view::PostQueryBuilder,
+    site_view::SiteView,
+    user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+    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,24 +495,41 @@ impl Perform for GetUserDetails {
     };
 
     let user_id = user.map(|u| u.id);
-    let user_fun = move |conn: &'_ _| {
-      match user_id {
-        // if there's a logged in user and it's the same id as the user whose details are being
-        // requested we need to use get_user_dangerous so it returns their email or other sensitive
-        // data hidden when viewing users other than yourself
-        Some(auth_user_id) => {
-          if user_details_id == auth_user_id {
-            UserView::get_user_dangerous(conn, auth_user_id)
-          } else {
-            UserView::get_user_secure(conn, user_details_id)
-          }
-        }
-        None => UserView::get_user_secure(conn, user_details_id),
+
+    let (user_view, user_dangerous) = if let Some(auth_user_id) = user_id {
+      if user_details_id == auth_user_id {
+        (
+          None,
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewDangerous::read(conn, auth_user_id)
+            })
+            .await??,
+          ),
+        )
+      } else {
+        (
+          Some(
+            blocking(context.pool(), move |conn| {
+              UserViewSafe::read(conn, user_details_id)
+            })
+            .await??,
+          ),
+          None,
+        )
       }
+    } else {
+      (
+        Some(
+          blocking(context.pool(), move |conn| {
+            UserViewSafe::read(conn, user_details_id)
+          })
+          .await??,
+        ),
+        None,
+      )
     };
 
-    let user_view = blocking(context.pool(), user_fun).await??;
-
     let page = data.page;
     let limit = data.limit;
     let saved_only = data.saved_only;
@@ -519,23 +540,23 @@ impl Perform for GetUserDetails {
         .sort(&sort)
         .show_nsfw(show_nsfw)
         .saved_only(saved_only)
-        .for_community_id(community_id)
+        .community_id(community_id)
         .my_user_id(user_id)
         .page(page)
         .limit(limit);
 
       let mut comments_query = CommentQueryBuilder::create(conn)
+        .my_user_id(user_id)
         .sort(&sort)
         .saved_only(saved_only)
-        .my_user_id(user_id)
         .page(page)
         .limit(limit);
 
       // If its saved only, you don't care what creator it was
       // Or, if its not saved, then you only want it for that specific creator
       if !saved_only {
-        posts_query = posts_query.for_creator_id(user_details_id);
-        comments_query = comments_query.for_creator_id(user_details_id);
+        posts_query = posts_query.creator_id(user_details_id);
+        comments_query = comments_query.creator_id(user_details_id);
       }
 
       let posts = posts_query.list()?;
@@ -556,7 +577,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 +624,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 +705,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??;
 
@@ -720,9 +743,11 @@ impl Perform for GetReplies {
     let unread_only = data.unread_only;
     let user_id = user.id;
     let replies = blocking(context.pool(), move |conn| {
-      ReplyQueryBuilder::create(conn, user_id)
+      CommentQueryBuilder::create(conn)
         .sort(&sort)
         .unread_only(unread_only)
+        .recipient_id(user_id)
+        .my_user_id(user_id)
         .page(page)
         .limit(limit)
         .list()
@@ -752,7 +777,9 @@ impl Perform for GetUserMentions {
     let unread_only = data.unread_only;
     let user_id = user.id;
     let mentions = blocking(context.pool(), move |conn| {
-      UserMentionQueryBuilder::create(conn, user_id)
+      UserMentionQueryBuilder::create(conn)
+        .recipient_id(user_id)
+        .my_user_id(user_id)
         .sort(&sort)
         .unread_only(unread_only)
         .page(page)
@@ -797,13 +824,11 @@ impl Perform for MarkUserMentionAsRead {
     let user_mention_id = read_user_mention.id;
     let user_id = user.id;
     let user_mention_view = blocking(context.pool(), move |conn| {
-      UserMentionView::read(conn, user_mention_id, user_id)
+      UserMentionView::read(conn, user_mention_id, Some(user_id))
     })
     .await??;
 
-    Ok(UserMentionResponse {
-      mention: user_mention_view,
-    })
+    Ok(UserMentionResponse { user_mention_view })
   }
 }
 
@@ -821,7 +846,9 @@ impl Perform for MarkAllAsRead {
 
     let user_id = user.id;
     let replies = blocking(context.pool(), move |conn| {
-      ReplyQueryBuilder::create(conn, user_id)
+      CommentQueryBuilder::create(conn)
+        .my_user_id(user_id)
+        .recipient_id(user_id)
         .unread_only(true)
         .page(1)
         .limit(999)
@@ -832,8 +859,8 @@ impl Perform for MarkAllAsRead {
     // TODO: this should probably be a bulk operation
     // Not easy to do as a bulk operation,
     // because recipient_id isn't in the comment table
-    for reply in &replies {
-      let reply_id = reply.id;
+    for comment_view in &replies {
+      let reply_id = comment_view.comment.id;
       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
       if blocking(context.pool(), mark_as_read).await?.is_err() {
         return Err(APIError::err("couldnt_update_comment").into());
index ff0fb8199f96bd8ab436f088462dee2de9154084..700a2653c130a4f617e830d3b664b0c097d8fa01 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,
+  },
+  views::comment_view::CommentView,
   Likeable,
 };
 use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs};
@@ -43,7 +45,7 @@ pub(crate) async fn receive_create_comment(
   .await??;
 
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -83,7 +85,7 @@ pub(crate) async fn receive_update_comment(
   .await??;
 
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -128,7 +130,7 @@ pub(crate) async fn receive_like_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -173,7 +175,7 @@ pub(crate) async fn receive_dislike_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -206,7 +208,7 @@ pub(crate) async fn receive_delete_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -239,7 +241,7 @@ pub(crate) async fn receive_remove_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
index 2ee8c6ea7bb529cb40131293da8612267a515829..85dcc143d8989e60c7badb68773ef18cfbf3dfff 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},
+  views::comment_view::CommentView,
   Likeable,
 };
 use lemmy_structs::{blocking, comment::CommentResponse};
@@ -33,7 +33,7 @@ pub(crate) async fn receive_undo_like_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -71,7 +71,7 @@ pub(crate) async fn receive_undo_dislike_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -104,7 +104,7 @@ pub(crate) async fn receive_undo_delete_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
@@ -137,7 +137,7 @@ pub(crate) async fn receive_undo_remove_comment(
   // TODO get those recipient actor ids from somewhere
   let recipient_ids = vec![];
   let res = CommentResponse {
-    comment: comment_view,
+    comment_view,
     recipient_ids,
     form_id: None,
   };
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..08735b4c287597e7fed4c59701d6ef98658e3363 100644 (file)
@@ -13,15 +13,19 @@ 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::{
+    comment_view::CommentView,
+    community_view::CommunityView,
+    post_view::PostView,
+    user_view::UserViewSafe,
+  },
   ApubObject,
   Joinable,
   SearchType,
@@ -161,7 +165,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/comment_aggregates.rs b/lemmy_db/src/aggregates/comment_aggregates.rs
new file mode 100644 (file)
index 0000000..7ce52ed
--- /dev/null
@@ -0,0 +1,23 @@
+use crate::schema::comment_aggregates;
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)]
+#[table_name = "comment_aggregates"]
+pub struct CommentAggregates {
+  pub id: i32,
+  pub comment_id: i32,
+  pub score: i64,
+  pub upvotes: i64,
+  pub downvotes: i64,
+}
+
+impl CommentAggregates {
+  pub fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+    comment_aggregates::table
+      .filter(comment_aggregates::comment_id.eq(comment_id))
+      .first::<Self>(conn)
+  }
+}
+
+// TODO add tests here
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..bdef659
--- /dev/null
@@ -0,0 +1,5 @@
+pub mod comment_aggregates;
+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,
 };
diff --git a/lemmy_db/src/comment_view.rs b/lemmy_db/src/comment_view.rs
deleted file mode 100644 (file)
index 4b6dc19..0000000
+++ /dev/null
@@ -1,719 +0,0 @@
-// TODO, remove the cross join here, just join to user directly
-use crate::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::{Deserialize, Serialize};
-
-// The faked schema since diesel doesn't do views
-table! {
-  comment_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-table! {
-  comment_fast_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_published -> Timestamp,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "comment_fast_view"]
-pub struct CommentView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub ap_id: String,
-  pub local: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: 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 score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub saved: Option<bool>,
-}
-
-pub struct CommentQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::comment_view::comment_fast_view::BoxedQuery<'a, Pg>,
-  listing_type: ListingType,
-  sort: &'a SortType,
-  for_community_id: Option<i32>,
-  for_community_name: Option<String>,
-  for_post_id: Option<i32>,
-  for_creator_id: Option<i32>,
-  search_term: Option<String>,
-  my_user_id: Option<i32>,
-  saved_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> CommentQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection) -> Self {
-    use super::comment_view::comment_fast_view::dsl::*;
-
-    let query = comment_fast_view.into_boxed();
-
-    CommentQueryBuilder {
-      conn,
-      query,
-      listing_type: ListingType::All,
-      sort: &SortType::New,
-      for_community_id: None,
-      for_community_name: None,
-      for_post_id: None,
-      for_creator_id: None,
-      search_term: None,
-      my_user_id: None,
-      saved_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn listing_type(mut self, listing_type: ListingType) -> Self {
-    self.listing_type = listing_type;
-    self
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn for_post_id<T: MaybeOptional<i32>>(mut self, for_post_id: T) -> Self {
-    self.for_post_id = for_post_id.get_optional();
-    self
-  }
-
-  pub fn for_creator_id<T: MaybeOptional<i32>>(mut self, for_creator_id: T) -> Self {
-    self.for_creator_id = for_creator_id.get_optional();
-    self
-  }
-
-  pub fn for_community_id<T: MaybeOptional<i32>>(mut self, for_community_id: T) -> Self {
-    self.for_community_id = for_community_id.get_optional();
-    self
-  }
-
-  pub fn for_community_name<T: MaybeOptional<String>>(mut self, for_community_name: T) -> Self {
-    self.for_community_name = for_community_name.get_optional();
-    self
-  }
-
-  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
-    self.search_term = search_term.get_optional();
-    self
-  }
-
-  pub fn 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 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<CommentView>, Error> {
-    use super::comment_view::comment_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    // The view lets you pass a null user_id, if you're not logged in
-    if let Some(my_user_id) = self.my_user_id {
-      query = query.filter(user_id.eq(my_user_id));
-    } else {
-      query = query.filter(user_id.is_null());
-    }
-
-    if let Some(for_creator_id) = self.for_creator_id {
-      query = query.filter(creator_id.eq(for_creator_id));
-    };
-
-    if let Some(for_community_id) = self.for_community_id {
-      query = query.filter(community_id.eq(for_community_id));
-    }
-
-    if let Some(for_community_name) = self.for_community_name {
-      query = query
-        .filter(community_name.eq(for_community_name))
-        .filter(local.eq(true));
-    }
-
-    if let Some(for_post_id) = self.for_post_id {
-      query = query.filter(post_id.eq(for_post_id));
-    };
-
-    if let Some(search_term) = self.search_term {
-      query = query.filter(content.ilike(fuzzy_search(&search_term)));
-    };
-
-    query = match self.listing_type {
-      ListingType::Subscribed => query.filter(subscribed.eq(true)),
-      ListingType::Local => query.filter(community_local.eq(true)),
-      _ => query,
-    };
-
-    if self.saved_only {
-      query = query.filter(saved.eq(true));
-    }
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      // _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-
-    // Note: deleted and removed comments are done on the front side
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<CommentView>(self.conn)
-  }
-}
-
-impl CommentView {
-  pub fn read(
-    conn: &PgConnection,
-    from_comment_id: i32,
-    my_user_id: Option<i32>,
-  ) -> Result<Self, Error> {
-    use super::comment_view::comment_fast_view::dsl::*;
-    let mut query = comment_fast_view.into_boxed();
-
-    // The view lets you pass a null user_id, if you're not logged in
-    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 = query
-      .filter(id.eq(from_comment_id))
-      .order_by(published.desc());
-
-    query.first::<Self>(conn)
-  }
-}
-
-// The faked schema since diesel doesn't do views
-table! {
-  reply_fast_view (id) {
-    id -> Int4,
-    creator_id -> Int4,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    ap_id -> Text,
-    local -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Varchar>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    creator_published -> Timestamp,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    subscribed -> Nullable<Bool>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-  }
-}
-
-#[derive(
-  Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone,
-)]
-#[table_name = "reply_fast_view"]
-pub struct ReplyView {
-  pub id: i32,
-  pub creator_id: i32,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub ap_id: String,
-  pub local: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  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 creator_published: chrono::NaiveDateTime,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub subscribed: Option<bool>,
-  pub saved: Option<bool>,
-  pub recipient_id: i32,
-}
-
-pub struct ReplyQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::comment_view::reply_fast_view::BoxedQuery<'a, Pg>,
-  for_user_id: i32,
-  sort: &'a SortType,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> ReplyQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
-    use super::comment_view::reply_fast_view::dsl::*;
-
-    let query = reply_fast_view.into_boxed();
-
-    ReplyQueryBuilder {
-      conn,
-      query,
-      for_user_id,
-      sort: &SortType::New,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn unread_only(mut self, unread_only: bool) -> Self {
-    self.unread_only = unread_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<ReplyView>, Error> {
-    use super::comment_view::reply_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    query = query
-      .filter(user_id.eq(self.for_user_id))
-      .filter(recipient_id.eq(self.for_user_id))
-      .filter(deleted.eq(false))
-      .filter(removed.eq(false));
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    }
-
-    query = match self.sort {
-      // SortType::Hot => query.order_by(hot_rank.desc()), // TODO why is this commented
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<ReplyView>(self.conn)
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use crate::{
-    comment::*,
-    comment_view::*,
-    community::*,
-    post::*,
-    tests::establish_unpooled_connection,
-    user::*,
-    Crud,
-    Likeable,
-    *,
-  };
-
-  #[test]
-  fn test_crud() {
-    let conn = establish_unpooled_connection();
-
-    let new_user = UserForm {
-      name: "timmy".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: "test community 5".to_string(),
-      title: "nada".to_owned(),
-      description: None,
-      category_id: 1,
-      creator_id: inserted_user.id,
-      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: "A test post 2".into(),
-      creator_id: inserted_user.id,
-      url: None,
-      body: None,
-      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 comment_form = CommentForm {
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      parent_id: None,
-      removed: None,
-      deleted: None,
-      read: None,
-      published: None,
-      updated: None,
-      ap_id: None,
-      local: true,
-    };
-
-    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
-
-    let comment_like_form = CommentLikeForm {
-      comment_id: inserted_comment.id,
-      post_id: inserted_post.id,
-      user_id: inserted_user.id,
-      score: 1,
-    };
-
-    let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
-
-    let expected_comment_view_no_user = CommentView {
-      id: inserted_comment.id,
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      post_name: inserted_post.name.to_owned(),
-      community_id: inserted_community.id,
-      community_name: inserted_community.name.to_owned(),
-      community_icon: None,
-      parent_id: None,
-      removed: false,
-      deleted: false,
-      read: false,
-      banned: false,
-      banned_from_community: false,
-      published: inserted_comment.published,
-      updated: None,
-      creator_name: inserted_user.name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      score: 1,
-      downvotes: 0,
-      hot_rank: 0,
-      hot_rank_active: 0,
-      upvotes: 1,
-      user_id: None,
-      my_vote: None,
-      subscribed: None,
-      saved: None,
-      ap_id: inserted_comment.ap_id.to_owned(),
-      local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-    };
-
-    let expected_comment_view_with_user = CommentView {
-      id: inserted_comment.id,
-      content: "A test comment 32".into(),
-      creator_id: inserted_user.id,
-      post_id: inserted_post.id,
-      post_name: inserted_post.name.to_owned(),
-      community_id: inserted_community.id,
-      community_name: inserted_community.name.to_owned(),
-      community_icon: None,
-      parent_id: None,
-      removed: false,
-      deleted: false,
-      read: false,
-      banned: false,
-      banned_from_community: false,
-      published: inserted_comment.published,
-      updated: None,
-      creator_name: inserted_user.name.to_owned(),
-      creator_preferred_username: None,
-      creator_published: inserted_user.published,
-      creator_avatar: None,
-      score: 1,
-      downvotes: 0,
-      hot_rank: 0,
-      hot_rank_active: 0,
-      upvotes: 1,
-      user_id: Some(inserted_user.id),
-      my_vote: Some(1),
-      subscribed: Some(false),
-      saved: Some(false),
-      ap_id: inserted_comment.ap_id.to_owned(),
-      local: true,
-      community_actor_id: inserted_community.actor_id.to_owned(),
-      community_local: true,
-      creator_actor_id: inserted_user.actor_id.to_owned(),
-      creator_local: true,
-    };
-
-    let mut read_comment_views_no_user = CommentQueryBuilder::create(&conn)
-      .for_post_id(inserted_post.id)
-      .list()
-      .unwrap();
-    read_comment_views_no_user[0].hot_rank = 0;
-    read_comment_views_no_user[0].hot_rank_active = 0;
-
-    let mut read_comment_views_with_user = CommentQueryBuilder::create(&conn)
-      .for_post_id(inserted_post.id)
-      .my_user_id(inserted_user.id)
-      .list()
-      .unwrap();
-    read_comment_views_with_user[0].hot_rank = 0;
-    read_comment_views_with_user[0].hot_rank_active = 0;
-
-    let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
-    let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
-    Post::delete(&conn, inserted_post.id).unwrap();
-    Community::delete(&conn, inserted_community.id).unwrap();
-    User_::delete(&conn, inserted_user.id).unwrap();
-
-    assert_eq!(expected_comment_view_no_user, read_comment_views_no_user[0]);
-    assert_eq!(
-      expected_comment_view_with_user,
-      read_comment_views_with_user[0]
-    );
-    assert_eq!(1, num_deleted);
-    assert_eq!(1, like_removed);
-  }
-}
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..ba5dccdf671c8f51881571de77058c1617aa759f 100644 (file)
@@ -11,28 +11,15 @@ 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 aggregates;
 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 source;
+pub mod views;
 
 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
 
@@ -149,6 +136,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 +214,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..cbfce876180faa4f45be4d67f7db44218830e17f 100644 (file)
@@ -34,6 +34,16 @@ table! {
     }
 }
 
+table! {
+    comment_aggregates (id) {
+        id -> Int4,
+        comment_id -> Int4,
+        score -> Int8,
+        upvotes -> Int8,
+        downvotes -> Int8,
+    }
+}
+
 table! {
     comment_aggregates_fast (id) {
         id -> Int4,
@@ -127,6 +137,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 +339,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 +472,16 @@ table! {
     }
 }
 
+table! {
+    site_aggregates (id) {
+        id -> Int4,
+        users -> Int8,
+        posts -> Int8,
+        comments -> Int8,
+        communities -> Int8,
+    }
+}
+
 table! {
     user_ (id) {
         id -> Int4,
@@ -471,6 +513,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,
@@ -513,8 +566,62 @@ table! {
     }
 }
 
+// These are necessary since diesel doesn't have self joins / aliases
+table! {
+    comment_alias_1 (id) {
+        id -> Int4,
+        creator_id -> Int4,
+        post_id -> Int4,
+        parent_id -> Nullable<Int4>,
+        content -> Text,
+        removed -> Bool,
+        read -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        deleted -> Bool,
+        ap_id -> Varchar,
+        local -> Bool,
+    }
+}
+
+table! {
+    user_alias_1 (id) {
+        id -> Int4,
+        name -> Varchar,
+        preferred_username -> Nullable<Varchar>,
+        password_encrypted -> Text,
+        email -> Nullable<Text>,
+        avatar -> Nullable<Text>,
+        admin -> Bool,
+        banned -> Bool,
+        published -> Timestamp,
+        updated -> Nullable<Timestamp>,
+        show_nsfw -> Bool,
+        theme -> Varchar,
+        default_sort_type -> Int2,
+        default_listing_type -> Int2,
+        lang -> Varchar,
+        show_avatars -> Bool,
+        send_notifications_to_email -> Bool,
+        matrix_user_id -> Nullable<Text>,
+        actor_id -> Varchar,
+        bio -> Nullable<Text>,
+        local -> Bool,
+        private_key -> Nullable<Text>,
+        public_key -> Nullable<Text>,
+        last_refreshed_at -> Timestamp,
+        banner -> Nullable<Text>,
+        deleted -> Bool,
+    }
+}
+
+joinable!(comment_alias_1 -> user_alias_1 (creator_id));
+joinable!(comment -> comment_alias_1 (parent_id));
+joinable!(user_mention -> user_alias_1 (recipient_id));
+
 joinable!(comment -> post (post_id));
 joinable!(comment -> user_ (creator_id));
+joinable!(comment_aggregates -> comment (comment_id));
 joinable!(comment_like -> comment (comment_id));
 joinable!(comment_like -> post (post_id));
 joinable!(comment_like -> user_ (user_id));
@@ -523,6 +630,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 +652,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 +661,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));
@@ -560,11 +670,13 @@ allow_tables_to_appear_in_same_query!(
   activity,
   category,
   comment,
+  comment_aggregates,
   comment_aggregates_fast,
   comment_like,
   comment_report,
   comment_saved,
   community,
+  community_aggregates,
   community_aggregates_fast,
   community_follower,
   community_moderator,
@@ -580,6 +692,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,8 +700,12 @@ allow_tables_to_appear_in_same_query!(
   post_saved,
   private_message,
   site,
+  site_aggregates,
   user_,
+  user_aggregates,
   user_ban,
   user_fast,
   user_mention,
+  comment_alias_1,
+  user_alias_1,
 );
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 94%
rename from lemmy_db/src/comment.rs
rename to lemmy_db/src/source/comment.rs
index 3e7d06be289afb3e7af56b43d377caffec50dfbf..380bdb3d157db6b1a259cc81dcf8416549c3cfa3 100644 (file)
@@ -1,13 +1,14 @@
 use super::post::Post;
 use crate::{
   naive_now,
-  schema::{comment, comment_like, comment_saved},
+  schema::{comment, comment_alias_1, comment_like, comment_saved},
   ApubObject,
   Crud,
   Likeable,
   Saveable,
 };
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 use url::{ParseError, Url};
 
 // WITH RECURSIVE MyTree AS (
@@ -17,7 +18,7 @@ use url::{ParseError, Url};
 // )
 // SELECT * FROM MyTree;
 
-#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[belongs_to(Post)]
 #[table_name = "comment"]
 pub struct Comment {
@@ -35,6 +36,24 @@ pub struct Comment {
   pub local: bool,
 }
 
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
+#[belongs_to(Post)]
+#[table_name = "comment_alias_1"]
+pub struct CommentAlias1 {
+  pub id: i32,
+  pub creator_id: i32,
+  pub post_id: i32,
+  pub parent_id: Option<i32>,
+  pub content: String,
+  pub removed: bool,
+  pub read: bool, // Whether the recipient has read the comment or not
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub deleted: bool,
+  pub ap_id: String,
+  pub local: bool,
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "comment"]
 pub struct CommentForm {
@@ -190,7 +209,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 +219,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,
 }
 
@@ -266,11 +285,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 35d54c3f182b9edfe967b59c28840d2a16625b4b..84db0c7c418c70f5056a69a867b0ae8a23123d99 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)
   }
 
@@ -352,7 +418,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 99%
rename from lemmy_db/src/post.rs
rename to lemmy_db/src/source/post.rs
index b42c23c72145aa47dd9eff1615198198e2b30488..098ce8835316db5a72a6c005773e3ce4a3581634 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,
@@ -337,10 +338,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 69%
rename from lemmy_db/src/user.rs
rename to lemmy_db/src/source/user.rs
index d8e833e6e5bdb2540e7d5d3777c821446edaa10e..0bd68a50924c57fa2b4e38465ebd75debabf1810 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
   is_email_regex,
   naive_now,
-  schema::{user_, user_::dsl::*},
+  schema::{user_, user_::dsl::*, user_alias_1},
   ApubObject,
   Crud,
 };
@@ -41,6 +41,160 @@ 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(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub password_encrypted: String,
+  pub email: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub show_nsfw: bool,
+  pub theme: String,
+  pub default_sort_type: i16,
+  pub default_listing_type: i16,
+  pub lang: String,
+  pub show_avatars: bool,
+  pub send_notifications_to_email: bool,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub private_key: Option<String>,
+  pub public_key: Option<String>,
+  pub last_refreshed_at: chrono::NaiveDateTime,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_alias_1"]
+pub struct UserSafeAlias1 {
+  pub id: i32,
+  pub name: String,
+  pub preferred_username: Option<String>,
+  pub avatar: Option<String>,
+  pub admin: bool,
+  pub banned: bool,
+  pub published: chrono::NaiveDateTime,
+  pub updated: Option<chrono::NaiveDateTime>,
+  pub matrix_user_id: Option<String>,
+  pub actor_id: String,
+  pub bio: Option<String>,
+  pub local: bool,
+  pub banner: Option<String>,
+  pub deleted: bool,
+}
+
+mod safe_type_alias {
+  use crate::{schema::user_alias_1::columns::*, source::user::UserAlias1, ToSafe};
+  type Columns = (
+    id,
+    name,
+    preferred_username,
+    avatar,
+    admin,
+    banned,
+    published,
+    updated,
+    matrix_user_id,
+    actor_id,
+    bio,
+    local,
+    banner,
+    deleted,
+  );
+
+  impl ToSafe for UserAlias1 {
+    type SafeColumns = Columns;
+    fn safe_columns_tuple() -> Self::SafeColumns {
+      (
+        id,
+        name,
+        preferred_username,
+        avatar,
+        admin,
+        banned,
+        published,
+        updated,
+        matrix_user_id,
+        actor_id,
+        bio,
+        local,
+        banner,
+        deleted,
+      )
+    }
+  }
+}
+
 #[derive(Insertable, AsChangeset, Clone)]
 #[table_name = "user_"]
 pub struct UserForm {
@@ -213,7 +367,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 +428,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 97%
rename from lemmy_db/src/user_mention.rs
rename to lemmy_db/src/source/user_mention.rs
index b382a24e029f21f980a7ec546d0b20e5fbe52b1c..bf53cb4200504854fbdaecce9515158fe1eec54a 100644 (file)
@@ -1,8 +1,9 @@
 use super::comment::Comment;
 use crate::{schema::user_mention, Crud};
 use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
 
-#[derive(Queryable, Associations, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
 #[belongs_to(Comment)]
 #[table_name = "user_mention"]
 pub struct UserMention {
@@ -78,12 +79,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_mention_view.rs b/lemmy_db/src/user_mention_view.rs
deleted file mode 100644 (file)
index d1ce5eb..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-use crate::{limit_and_offset, MaybeOptional, SortType};
-use diesel::{dsl::*, pg::Pg, result::Error, *};
-use serde::Serialize;
-
-// The faked schema since diesel doesn't do views
-table! {
-  user_mention_view (id) {
-    id -> Int4,
-    user_mention_id -> Int4,
-    creator_id -> Int4,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-    recipient_actor_id -> Text,
-    recipient_local -> Bool,
-  }
-}
-
-table! {
-  user_mention_fast_view (id) {
-    id -> Int4,
-    user_mention_id -> Int4,
-    creator_id -> Int4,
-    creator_actor_id -> Text,
-    creator_local -> Bool,
-    post_id -> Int4,
-    post_name -> Varchar,
-    parent_id -> Nullable<Int4>,
-    content -> Text,
-    removed -> Bool,
-    read -> Bool,
-    published -> Timestamp,
-    updated -> Nullable<Timestamp>,
-    deleted -> Bool,
-    community_id -> Int4,
-    community_actor_id -> Text,
-    community_local -> Bool,
-    community_name -> Varchar,
-    community_icon -> Nullable<Text>,
-    banned -> Bool,
-    banned_from_community -> Bool,
-    creator_name -> Varchar,
-    creator_preferred_username -> Nullable<Varchar>,
-    creator_avatar -> Nullable<Text>,
-    score -> BigInt,
-    upvotes -> BigInt,
-    downvotes -> BigInt,
-    hot_rank -> Int4,
-    hot_rank_active -> Int4,
-    user_id -> Nullable<Int4>,
-    my_vote -> Nullable<Int4>,
-    saved -> Nullable<Bool>,
-    recipient_id -> Int4,
-    recipient_actor_id -> Text,
-    recipient_local -> Bool,
-  }
-}
-
-#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)]
-#[table_name = "user_mention_fast_view"]
-pub struct UserMentionView {
-  pub id: i32,
-  pub user_mention_id: i32,
-  pub creator_id: i32,
-  pub creator_actor_id: String,
-  pub creator_local: bool,
-  pub post_id: i32,
-  pub post_name: String,
-  pub parent_id: Option<i32>,
-  pub content: String,
-  pub removed: bool,
-  pub read: bool,
-  pub published: chrono::NaiveDateTime,
-  pub updated: Option<chrono::NaiveDateTime>,
-  pub deleted: bool,
-  pub community_id: i32,
-  pub community_actor_id: String,
-  pub community_local: bool,
-  pub community_name: String,
-  pub community_icon: Option<String>,
-  pub banned: bool,
-  pub banned_from_community: bool,
-  pub creator_name: String,
-  pub creator_preferred_username: Option<String>,
-  pub creator_avatar: Option<String>,
-  pub score: i64,
-  pub upvotes: i64,
-  pub downvotes: i64,
-  pub hot_rank: i32,
-  pub hot_rank_active: i32,
-  pub user_id: Option<i32>,
-  pub my_vote: Option<i32>,
-  pub saved: Option<bool>,
-  pub recipient_id: i32,
-  pub recipient_actor_id: String,
-  pub recipient_local: bool,
-}
-
-pub struct UserMentionQueryBuilder<'a> {
-  conn: &'a PgConnection,
-  query: super::user_mention_view::user_mention_fast_view::BoxedQuery<'a, Pg>,
-  for_user_id: i32,
-  sort: &'a SortType,
-  unread_only: bool,
-  page: Option<i64>,
-  limit: Option<i64>,
-}
-
-impl<'a> UserMentionQueryBuilder<'a> {
-  pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    let query = user_mention_fast_view.into_boxed();
-
-    UserMentionQueryBuilder {
-      conn,
-      query,
-      for_user_id,
-      sort: &SortType::New,
-      unread_only: false,
-      page: None,
-      limit: None,
-    }
-  }
-
-  pub fn sort(mut self, sort: &'a SortType) -> Self {
-    self.sort = sort;
-    self
-  }
-
-  pub fn unread_only(mut self, unread_only: bool) -> Self {
-    self.unread_only = unread_only;
-    self
-  }
-
-  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
-    self.page = page.get_optional();
-    self
-  }
-
-  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
-    self.limit = limit.get_optional();
-    self
-  }
-
-  pub fn list(self) -> Result<Vec<UserMentionView>, Error> {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    let mut query = self.query;
-
-    if self.unread_only {
-      query = query.filter(read.eq(false));
-    }
-
-    query = query
-      .filter(user_id.eq(self.for_user_id))
-      .filter(recipient_id.eq(self.for_user_id));
-
-    query = match self.sort {
-      SortType::Hot => query
-        .order_by(hot_rank.desc())
-        .then_order_by(published.desc()),
-      SortType::Active => query
-        .order_by(hot_rank_active.desc())
-        .then_order_by(published.desc()),
-      SortType::New => query.order_by(published.desc()),
-      SortType::TopAll => query.order_by(score.desc()),
-      SortType::TopYear => query
-        .filter(published.gt(now - 1.years()))
-        .order_by(score.desc()),
-      SortType::TopMonth => query
-        .filter(published.gt(now - 1.months()))
-        .order_by(score.desc()),
-      SortType::TopWeek => query
-        .filter(published.gt(now - 1.weeks()))
-        .order_by(score.desc()),
-      SortType::TopDay => query
-        .filter(published.gt(now - 1.days()))
-        .order_by(score.desc()),
-      // _ => query.order_by(published.desc()),
-    };
-
-    let (limit, offset) = limit_and_offset(self.page, self.limit);
-    query
-      .limit(limit)
-      .offset(offset)
-      .load::<UserMentionView>(self.conn)
-  }
-}
-
-impl UserMentionView {
-  pub fn read(
-    conn: &PgConnection,
-    from_user_mention_id: i32,
-    from_recipient_id: i32,
-  ) -> Result<Self, Error> {
-    use super::user_mention_view::user_mention_fast_view::dsl::*;
-
-    user_mention_fast_view
-      .filter(user_mention_id.eq(from_user_mention_id))
-      .filter(user_id.eq(from_recipient_id))
-      .first::<Self>(conn)
-  }
-}
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/comment_view.rs b/lemmy_db/src/views/comment_view.rs
new file mode 100644 (file)
index 0000000..d85f654
--- /dev/null
@@ -0,0 +1,633 @@
+use crate::{
+  aggregates::comment_aggregates::CommentAggregates,
+  functions::hot_rank,
+  fuzzy_search,
+  limit_and_offset,
+  schema::{
+    comment,
+    comment_aggregates,
+    comment_alias_1,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+  },
+  source::{
+    comment::{Comment, CommentAlias1, CommentSaved},
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+  },
+  views::ViewToVec,
+  ListingType,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct CommentView {
+  pub comment: Comment,
+  pub creator: UserSafe,
+  pub recipient: Option<UserSafeAlias1>, // Left joins to comment and user
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub saved: bool,                         // Left join to CommentSaved
+  pub my_vote: Option<i16>,                // Left join to CommentLike
+}
+
+type CommentViewTuple = (
+  Comment,
+  UserSafe,
+  Option<CommentAlias1>,
+  Option<UserSafeAlias1>,
+  Post,
+  CommunitySafe,
+  CommentAggregates,
+  Option<CommunityUserBan>,
+  Option<CommunityFollower>,
+  Option<CommentSaved>,
+  Option<i16>,
+);
+
+impl CommentView {
+  pub fn read(
+    conn: &PgConnection,
+    comment_id: i32,
+    my_user_id: Option<i32>,
+  ) -> Result<Self, Error> {
+    // The left join below will return None in this case
+    let user_id_join = my_user_id.unwrap_or(-1);
+
+    let (
+      comment,
+      creator,
+      _parent_comment,
+      recipient,
+      post,
+      community,
+      counts,
+      creator_banned_from_community,
+      subscribed,
+      saved,
+      my_vote,
+    ) = comment::table
+      .find(comment_id)
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .first::<CommentViewTuple>(conn)?;
+
+    Ok(CommentView {
+      comment,
+      recipient,
+      post,
+      creator,
+      community,
+      counts,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      subscribed: subscribed.is_some(),
+      saved: saved.is_some(),
+      my_vote,
+    })
+  }
+}
+
+pub struct CommentQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  listing_type: ListingType,
+  sort: &'a SortType,
+  community_id: Option<i32>,
+  community_name: Option<String>,
+  post_id: Option<i32>,
+  creator_id: Option<i32>,
+  recipient_id: Option<i32>,
+  my_user_id: Option<i32>,
+  search_term: Option<String>,
+  saved_only: bool,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> CommentQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    CommentQueryBuilder {
+      conn,
+      listing_type: ListingType::All,
+      sort: &SortType::New,
+      community_id: None,
+      community_name: None,
+      post_id: None,
+      creator_id: None,
+      recipient_id: None,
+      my_user_id: None,
+      search_term: None,
+      saved_only: false,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn listing_type(mut self, listing_type: ListingType) -> Self {
+    self.listing_type = listing_type;
+    self
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn post_id<T: MaybeOptional<i32>>(mut self, post_id: T) -> Self {
+    self.post_id = post_id.get_optional();
+    self
+  }
+
+  pub fn creator_id<T: MaybeOptional<i32>>(mut self, creator_id: T) -> Self {
+    self.creator_id = creator_id.get_optional();
+    self
+  }
+
+  pub fn recipient_id<T: MaybeOptional<i32>>(mut self, recipient_id: T) -> Self {
+    self.recipient_id = recipient_id.get_optional();
+    self
+  }
+
+  pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+    self.community_id = community_id.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 community_name<T: MaybeOptional<String>>(mut self, community_name: T) -> Self {
+    self.community_name = community_name.get_optional();
+    self
+  }
+
+  pub fn search_term<T: MaybeOptional<String>>(mut self, search_term: T) -> Self {
+    self.search_term = search_term.get_optional();
+    self
+  }
+
+  pub fn saved_only(mut self, saved_only: bool) -> Self {
+    self.saved_only = saved_only;
+    self
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+    self.page = page.get_optional();
+    self
+  }
+
+  pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+    self.limit = limit.get_optional();
+    self
+  }
+
+  pub fn list(self) -> Result<Vec<CommentView>, Error> {
+    use diesel::dsl::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = comment::table
+      .inner_join(user_::table)
+      // recipient here
+      .left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
+      .left_join(user_alias_1::table.on(user_alias_1::id.eq(comment_alias_1::creator_id)))
+      .inner_join(post::table)
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(comment_aggregates::table)
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        comment_alias_1::all_columns.nullable(),
+        UserAlias1::safe_columns_tuple().nullable(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    // The replies
+    if let Some(recipient_id) = self.recipient_id {
+      query = query
+        // TODO needs lots of testing
+        .filter(user_alias_1::id.eq(recipient_id))
+        .filter(comment::deleted.eq(false))
+        .filter(comment::removed.eq(false));
+    }
+
+    if self.unread_only {
+      query = query.filter(comment::read.eq(false));
+    }
+
+    if let Some(creator_id) = self.creator_id {
+      query = query.filter(comment::creator_id.eq(creator_id));
+    };
+
+    if let Some(community_id) = self.community_id {
+      query = query.filter(post::community_id.eq(community_id));
+    }
+
+    if let Some(community_name) = self.community_name {
+      query = query
+        .filter(community::name.eq(community_name))
+        .filter(comment::local.eq(true));
+    }
+
+    if let Some(post_id) = self.post_id {
+      query = query.filter(comment::post_id.eq(post_id));
+    };
+
+    if let Some(search_term) = self.search_term {
+      query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
+    };
+
+    query = match self.listing_type {
+      // ListingType::Subscribed => query.filter(community_follower::subscribed.eq(true)),
+      ListingType::Subscribed => query.filter(community_follower::user_id.is_not_null()), // TODO could be this: and(community_follower::user_id.eq(user_id_join)),
+      ListingType::Local => query.filter(community::local.eq(true)),
+      _ => query,
+    };
+
+    if self.saved_only {
+      query = query.filter(comment_saved::id.is_not_null());
+    }
+
+    query = match self.sort {
+      SortType::Hot | SortType::Active => query
+        .order_by(hot_rank(comment_aggregates::score, comment::published).desc())
+        .then_order_by(comment::published.desc()),
+      SortType::New => query.order_by(comment::published.desc()),
+      SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(comment::published.gt(now - 1.years()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(comment::published.gt(now - 1.months()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(comment::published.gt(now - 1.weeks()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(comment::published.gt(now - 1.days()))
+        .order_by(comment_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    // Note: deleted and removed comments are done on the front side
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .load::<CommentViewTuple>(self.conn)?;
+
+    Ok(CommentView::to_vec(res))
+  }
+}
+
+impl ViewToVec for CommentView {
+  type DbTuple = CommentViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        comment: a.0.to_owned(),
+        creator: a.1.to_owned(),
+        recipient: a.3.to_owned(),
+        post: a.4.to_owned(),
+        community: a.5.to_owned(),
+        counts: a.6.to_owned(),
+        creator_banned_from_community: a.7.is_some(),
+        subscribed: a.8.is_some(),
+        saved: a.9.is_some(),
+        my_vote: a.10,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::{
+    source::{comment::*, community::*, post::*, user::*},
+    tests::establish_unpooled_connection,
+    views::comment_view::*,
+    Crud,
+    Likeable,
+    *,
+  };
+
+  #[test]
+  fn test_crud() {
+    let conn = establish_unpooled_connection();
+
+    let new_user = UserForm {
+      name: "timmy".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: "test community 5".to_string(),
+      title: "nada".to_owned(),
+      description: None,
+      category_id: 1,
+      creator_id: inserted_user.id,
+      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: "A test post 2".into(),
+      creator_id: inserted_user.id,
+      url: None,
+      body: None,
+      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 comment_form = CommentForm {
+      content: "A test comment 32".into(),
+      creator_id: inserted_user.id,
+      post_id: inserted_post.id,
+      parent_id: None,
+      removed: None,
+      deleted: None,
+      read: None,
+      published: None,
+      updated: None,
+      ap_id: None,
+      local: true,
+    };
+
+    let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
+
+    let comment_like_form = CommentLikeForm {
+      comment_id: inserted_comment.id,
+      post_id: inserted_post.id,
+      user_id: inserted_user.id,
+      score: 1,
+    };
+
+    let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
+
+    let expected_comment_view_no_user = CommentView {
+      creator_banned_from_community: false,
+      my_vote: None,
+      subscribed: false,
+      saved: false,
+      comment: Comment {
+        id: inserted_comment.id,
+        content: "A test comment 32".into(),
+        creator_id: inserted_user.id,
+        post_id: inserted_post.id,
+        parent_id: None,
+        removed: false,
+        deleted: false,
+        read: false,
+        published: inserted_comment.published,
+        ap_id: inserted_comment.ap_id,
+        updated: None,
+        local: true,
+      },
+      creator: UserSafe {
+        id: inserted_user.id,
+        name: "timmy".into(),
+        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,
+      },
+      recipient: None,
+      post: Post {
+        id: inserted_post.id,
+        name: inserted_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,
+      },
+      community: CommunitySafe {
+        id: inserted_community.id,
+        name: "test community 5".to_string(),
+        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: CommentAggregates {
+        id: inserted_comment.id, // TODO
+        comment_id: inserted_comment.id,
+        score: 1,
+        upvotes: 1,
+        downvotes: 0,
+      },
+    };
+
+    let mut expected_comment_view_with_user = expected_comment_view_no_user.to_owned();
+    expected_comment_view_with_user.my_vote = Some(1);
+
+    let read_comment_views_no_user = CommentQueryBuilder::create(&conn)
+      .post_id(inserted_post.id)
+      .list()
+      .unwrap();
+
+    let read_comment_views_with_user = CommentQueryBuilder::create(&conn)
+      .post_id(inserted_post.id)
+      .my_user_id(inserted_user.id)
+      .list()
+      .unwrap();
+
+    let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
+    let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
+    Post::delete(&conn, inserted_post.id).unwrap();
+    Community::delete(&conn, inserted_community.id).unwrap();
+    User_::delete(&conn, inserted_user.id).unwrap();
+
+    assert_eq!(expected_comment_view_no_user, read_comment_views_no_user[0]);
+    assert_eq!(
+      expected_comment_view_with_user,
+      read_comment_views_with_user[0]
+    );
+    assert_eq!(1, num_deleted);
+    assert_eq!(1, like_removed);
+  }
+}
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..7de9cc3
--- /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, 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(community_id))
+      .order_by(community_follower::published)
+      .load::<CommunityFollowerViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, 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(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..751e462
--- /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, 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(community_id))
+      .order_by(community_moderator::published)
+      .load::<CommunityModeratorViewTuple>(conn)?;
+
+    Ok(Self::to_vec(res))
+  }
+
+  pub fn for_user(conn: &PgConnection, 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(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..fcc707c
--- /dev/null
@@ -0,0 +1,204 @@
+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,
+    })
+  }
+}
+
+pub struct CommunityQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  sort: &'a SortType,
+  my_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 {
+    CommunityQueryBuilder {
+      conn,
+      my_user_id: None,
+      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 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 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> {
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut 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();
+
+    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..2516cae
--- /dev/null
@@ -0,0 +1,16 @@
+pub mod comment_view;
+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_mention_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..f95cf11
--- /dev/null
@@ -0,0 +1,634 @@
+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 creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub counts: PostAggregates,
+  pub subscribed: bool,     // Left join to CommunityFollower
+  pub saved: bool,          // Left join to PostSaved
+  pub read: bool,           // Left join to PostRead
+  pub my_vote: Option<i16>, // Left join to PostLike
+}
+
+type PostViewTuple = (
+  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(post::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,
+    })
+  }
+}
+
+pub struct PostQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  listing_type: &'a ListingType,
+  sort: &'a SortType,
+  creator_id: Option<i32>,
+  community_id: Option<i32>,
+  community_name: Option<String>,
+  my_user_id: Option<i32>,
+  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 {
+    PostQueryBuilder {
+      conn,
+      listing_type: &ListingType::All,
+      sort: &SortType::Hot,
+      creator_id: None,
+      community_id: None,
+      community_name: None,
+      my_user_id: 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 community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+    self.community_id = community_id.get_optional();
+    self
+  }
+
+  pub fn my_user_id<T: MaybeOptional<i32>>(mut self, my_user_id: T) -> Self {
+    self.community_id = my_user_id.get_optional();
+    self
+  }
+
+  pub fn community_name<T: MaybeOptional<String>>(mut self, community_name: T) -> Self {
+    self.community_name = community_name.get_optional();
+    self
+  }
+
+  pub fn creator_id<T: MaybeOptional<i32>>(mut self, creator_id: T) -> Self {
+    self.creator_id = 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::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut 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();
+
+    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(community_id) = self.community_id {
+      query = query
+        .filter(post::community_id.eq(community_id))
+        .then_order_by(post::stickied.desc());
+    }
+
+    if let Some(community_name) = self.community_name {
+      query = query
+        .filter(community::name.eq(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)),
+      );
+    }
+
+    // If its for a specific user, show the removed / deleted
+    if let Some(creator_id) = self.creator_id {
+      query = query.filter(post::creator_id.eq(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());
+    };
+
+    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()),
+    };
+
+    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)
+      .listing_type(&ListingType::Community)
+      .sort(&SortType::New)
+      .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)
+      .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_mention_view.rs b/lemmy_db/src/views/user_mention_view.rs
new file mode 100644 (file)
index 0000000..67616fb
--- /dev/null
@@ -0,0 +1,318 @@
+use crate::{
+  aggregates::comment_aggregates::CommentAggregates,
+  functions::hot_rank,
+  limit_and_offset,
+  schema::{
+    comment,
+    comment_aggregates,
+    comment_like,
+    comment_saved,
+    community,
+    community_follower,
+    community_user_ban,
+    post,
+    user_,
+    user_alias_1,
+    user_mention,
+  },
+  source::{
+    comment::{Comment, CommentSaved},
+    community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan},
+    post::Post,
+    user::{UserAlias1, UserSafe, UserSafeAlias1, User_},
+    user_mention::UserMention,
+  },
+  views::ViewToVec,
+  MaybeOptional,
+  SortType,
+  ToSafe,
+};
+use diesel::{result::Error, *};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+pub struct UserMentionView {
+  pub user_mention: UserMention,
+  pub comment: Comment,
+  pub creator: UserSafe,
+  pub post: Post,
+  pub community: CommunitySafe,
+  pub recipient: UserSafeAlias1,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool, // Left Join to CommunityUserBan
+  pub subscribed: bool,                    // Left join to CommunityFollower
+  pub saved: bool,                         // Left join to CommentSaved
+  pub my_vote: Option<i16>,                // Left join to CommentLike
+}
+
+type UserMentionViewTuple = (
+  UserMention,
+  Comment,
+  UserSafe,
+  Post,
+  CommunitySafe,
+  UserSafeAlias1,
+  CommentAggregates,
+  Option<CommunityUserBan>,
+  Option<CommunityFollower>,
+  Option<CommentSaved>,
+  Option<i16>,
+);
+
+impl UserMentionView {
+  pub fn read(
+    conn: &PgConnection,
+    user_mention_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 (
+      user_mention,
+      comment,
+      creator,
+      post,
+      community,
+      recipient,
+      counts,
+      creator_banned_from_community,
+      subscribed,
+      saved,
+      my_vote,
+    ) = user_mention::table
+      .find(user_mention_id)
+      .inner_join(comment::table)
+      .inner_join(user_::table.on(comment::creator_id.eq(user_::id)))
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_alias_1::table)
+      .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        user_mention::all_columns,
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .first::<UserMentionViewTuple>(conn)?;
+
+    Ok(UserMentionView {
+      user_mention,
+      comment,
+      creator,
+      post,
+      community,
+      recipient,
+      counts,
+      creator_banned_from_community: creator_banned_from_community.is_some(),
+      subscribed: subscribed.is_some(),
+      saved: saved.is_some(),
+      my_vote,
+    })
+  }
+}
+
+pub struct UserMentionQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  my_user_id: Option<i32>,
+  recipient_id: Option<i32>,
+  sort: &'a SortType,
+  unread_only: bool,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> UserMentionQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    UserMentionQueryBuilder {
+      conn,
+      my_user_id: None,
+      recipient_id: None,
+      sort: &SortType::New,
+      unread_only: false,
+      page: None,
+      limit: None,
+    }
+  }
+
+  pub fn sort(mut self, sort: &'a SortType) -> Self {
+    self.sort = sort;
+    self
+  }
+
+  pub fn unread_only(mut self, unread_only: bool) -> Self {
+    self.unread_only = unread_only;
+    self
+  }
+
+  pub fn recipient_id<T: MaybeOptional<i32>>(mut self, recipient_id: T) -> Self {
+    self.recipient_id = recipient_id.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 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<UserMentionView>, Error> {
+    use diesel::dsl::*;
+
+    // The left join below will return None in this case
+    let user_id_join = self.my_user_id.unwrap_or(-1);
+
+    let mut query = user_mention::table
+      .inner_join(comment::table)
+      .inner_join(user_::table.on(comment::creator_id.eq(user_::id)))
+      .inner_join(post::table.on(comment::post_id.eq(post::id)))
+      .inner_join(community::table.on(post::community_id.eq(community::id)))
+      .inner_join(user_alias_1::table)
+      .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
+      .left_join(
+        community_user_ban::table.on(
+          community::id
+            .eq(community_user_ban::community_id)
+            .and(community_user_ban::user_id.eq(comment::creator_id)),
+        ),
+      )
+      .left_join(
+        community_follower::table.on(
+          post::community_id
+            .eq(community_follower::community_id)
+            .and(community_follower::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_saved::table.on(
+          comment::id
+            .eq(comment_saved::comment_id)
+            .and(comment_saved::user_id.eq(user_id_join)),
+        ),
+      )
+      .left_join(
+        comment_like::table.on(
+          comment::id
+            .eq(comment_like::comment_id)
+            .and(comment_like::user_id.eq(user_id_join)),
+        ),
+      )
+      .select((
+        user_mention::all_columns,
+        comment::all_columns,
+        User_::safe_columns_tuple(),
+        post::all_columns,
+        Community::safe_columns_tuple(),
+        UserAlias1::safe_columns_tuple(),
+        comment_aggregates::all_columns,
+        community_user_ban::all_columns.nullable(),
+        community_follower::all_columns.nullable(),
+        comment_saved::all_columns.nullable(),
+        comment_like::score.nullable(),
+      ))
+      .into_boxed();
+
+    if let Some(recipient_id) = self.recipient_id {
+      query = query.filter(user_mention::recipient_id.eq(recipient_id));
+    }
+
+    if self.unread_only {
+      query = query.filter(user_mention::read.eq(false));
+    }
+
+    query = match self.sort {
+      SortType::Hot | SortType::Active => query
+        .order_by(hot_rank(comment_aggregates::score, comment::published).desc())
+        .then_order_by(comment::published.desc()),
+      SortType::New => query.order_by(comment::published.desc()),
+      SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
+      SortType::TopYear => query
+        .filter(comment::published.gt(now - 1.years()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopMonth => query
+        .filter(comment::published.gt(now - 1.months()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopWeek => query
+        .filter(comment::published.gt(now - 1.weeks()))
+        .order_by(comment_aggregates::score.desc()),
+      SortType::TopDay => query
+        .filter(comment::published.gt(now - 1.days()))
+        .order_by(comment_aggregates::score.desc()),
+    };
+
+    let (limit, offset) = limit_and_offset(self.page, self.limit);
+
+    let res = query
+      .limit(limit)
+      .offset(offset)
+      .load::<UserMentionViewTuple>(self.conn)?;
+
+    Ok(UserMentionView::to_vec(res))
+  }
+}
+
+impl ViewToVec for UserMentionView {
+  type DbTuple = UserMentionViewTuple;
+  fn to_vec(posts: Vec<Self::DbTuple>) -> Vec<Self> {
+    posts
+      .iter()
+      .map(|a| Self {
+        user_mention: a.0.to_owned(),
+        comment: a.1.to_owned(),
+        creator: a.2.to_owned(),
+        post: a.3.to_owned(),
+        community: a.4.to_owned(),
+        recipient: a.5.to_owned(),
+        counts: a.6.to_owned(),
+        creator_banned_from_community: a.7.is_some(),
+        subscribed: a.8.is_some(),
+        saved: a.9.is_some(),
+        my_vote: a.10,
+      })
+      .collect::<Vec<Self>>()
+  }
+}
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..587ebf6
--- /dev/null
@@ -0,0 +1,165 @@
+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))
+  }
+}
+
+pub struct UserQueryBuilder<'a> {
+  conn: &'a PgConnection,
+  sort: &'a SortType,
+  search_term: Option<String>,
+  page: Option<i64>,
+  limit: Option<i64>,
+}
+
+impl<'a> UserQueryBuilder<'a> {
+  pub fn create(conn: &'a PgConnection) -> Self {
+    UserQueryBuilder {
+      conn,
+      search_term: None,
+      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 {
+    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<UserViewSafe>, Error> {
+    let mut query = user_::table
+      .inner_join(user_aggregates::table)
+      .select((User_::safe_columns_tuple(), user_aggregates::all_columns))
+      .into_boxed();
+
+    if let Some(search_term) = self.search_term {
+      query = query.filter(user_::name.ilike(fuzzy_search(&search_term)));
+    }
+
+    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 6479124f8eca606ba290e1b6fcd53f0e08c4cd44..277499f49747c2bab900e991e898a7367e2b0525 100644 (file)
@@ -1,4 +1,4 @@
-use lemmy_db::{comment_report::CommentReportView, comment_view::CommentView};
+use lemmy_db::{comment_report::CommentReportView, views::comment_view::CommentView};
 use serde::{Deserialize, Serialize};
 
 #[derive(Deserialize)]
@@ -49,7 +49,7 @@ pub struct SaveComment {
 
 #[derive(Serialize, Clone)]
 pub struct CommentResponse {
-  pub comment: CommentView,
+  pub comment_view: CommentView,
   pub recipient_ids: Vec<i32>,
   pub form_id: Option<String>,
 }
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 a0dbdab651e2a675fda724ea8aa9dd0174313ed8..dc06a40cd1c05415353bf5b08b203998df6c8829 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..eea107a7ead840a19111f3b10d2d3bec2f63b417 100644 (file)
@@ -1,8 +1,11 @@
 use lemmy_db::{
-  comment_view::CommentView,
-  community_view::{CommunityModeratorView, CommunityView},
   post_report::PostReportView,
-  post_view::PostView,
+  views::{
+    comment_view::CommentView,
+    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..f32b841346563987343b237876aeeff0fa97881e 100644 (file)
@@ -1,12 +1,14 @@
 use lemmy_db::{
-  category::*,
-  comment_view::*,
-  community_view::*,
+  aggregates::site_aggregates::SiteAggregates,
   moderator_views::*,
-  post_view::*,
-  site_view::*,
-  user::*,
-  user_view::*,
+  source::{category::*, user::*},
+  views::{
+    comment_view::CommentView,
+    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..9ebb14f4c32961398ddde8838f0aae1087ad09d3 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::{
+    comment_view::CommentView,
+    community_follower_view::CommunityFollowerView,
+    community_moderator_view::CommunityModeratorView,
+    post_view::PostView,
+    user_mention_view::UserMentionView,
+    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>,
@@ -90,7 +94,7 @@ pub struct GetUserDetailsResponse {
 
 #[derive(Serialize)]
 pub struct GetRepliesResponse {
-  pub replies: Vec<ReplyView>,
+  pub replies: Vec<CommentView>,
 }
 
 #[derive(Serialize)]
@@ -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,
 }
 
@@ -158,7 +162,7 @@ pub struct MarkUserMentionAsRead {
 
 #[derive(Serialize, Clone)]
 pub struct UserMentionResponse {
-  pub mention: UserMentionView,
+  pub user_mention_view: UserMentionView,
 }
 
 #[derive(Deserialize)]
index 0be54c33f8fb2241bf3ac41811aad652195d8699..ece5d3534fccec3e423b6c997bbd81b25689b885 100644 (file)
@@ -328,9 +328,10 @@ impl ChatServer {
     comment: &CommentResponse,
     websocket_id: Option<ConnectionId>,
   ) -> Result<(), LemmyError> {
-    let mut comment_reply_sent = comment.clone();
-    comment_reply_sent.comment.my_vote = None;
-    comment_reply_sent.comment.user_id = None;
+    let comment_reply_sent = comment.clone();
+    // TODO what is this here
+    // comment_reply_sent.comment_view.my_vote = None;
+    // comment_reply_sent.comment.user_id = None;
 
     let mut comment_post_sent = comment_reply_sent.clone();
     comment_post_sent.recipient_ids = Vec::new();
@@ -339,7 +340,7 @@ impl ChatServer {
     self.send_post_room_message(
       user_operation,
       &comment_post_sent,
-      comment_post_sent.comment.post_id,
+      comment_post_sent.comment_view.post.id,
       websocket_id,
     )?;
 
@@ -358,7 +359,7 @@ impl ChatServer {
     self.send_community_room_message(
       user_operation,
       &comment_post_sent,
-      comment.comment.community_id,
+      comment.comment_view.community.id,
       websocket_id,
     )?;
 
@@ -368,22 +369,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();
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/down.sql b/migrations/2020-12-14-020038_create_comment_aggregates/down.sql
new file mode 100644 (file)
index 0000000..6fd9ddc
--- /dev/null
@@ -0,0 +1,7 @@
+-- comment aggregates
+drop table comment_aggregates;
+drop trigger comment_aggregates_comment on comment;
+drop trigger comment_aggregates_score on comment_like;
+drop function 
+  comment_aggregates_comment,
+  comment_aggregates_score;
diff --git a/migrations/2020-12-14-020038_create_comment_aggregates/up.sql b/migrations/2020-12-14-020038_create_comment_aggregates/up.sql
new file mode 100644 (file)
index 0000000..1a168be
--- /dev/null
@@ -0,0 +1,82 @@
+-- Add comment aggregates
+create table comment_aggregates (
+  id serial primary key,
+  comment_id int references comment on update cascade on delete cascade not null,
+  score bigint not null default 0,
+  upvotes bigint not null default 0,
+  downvotes bigint not null default 0,
+  unique (comment_id)
+);
+
+insert into comment_aggregates (comment_id, score, upvotes, downvotes)
+  select 
+    c.id,
+    COALESCE(cl.total, 0::bigint) AS score,
+    COALESCE(cl.up, 0::bigint) AS upvotes,
+    COALESCE(cl.down, 0::bigint) AS downvotes
+  from comment c
+  left join ( select l.comment_id as id,
+    sum(l.score) as total,
+    count(
+      case
+      when l.score = 1 then 1
+      else null::integer
+      end) as up,
+    count(
+      case
+      when l.score = '-1'::integer then 1
+      else null::integer
+      end) as down
+    from comment_like l
+    group by l.comment_id) cl on cl.id = c.id;
+
+-- Add comment aggregate triggers
+
+-- initial comment add
+create function comment_aggregates_comment()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    insert into comment_aggregates (comment_id) values (NEW.id);
+  ELSIF (TG_OP = 'DELETE') THEN
+    delete from comment_aggregates where comment_id = OLD.id;
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_comment
+after insert or delete on comment
+for each row
+execute procedure comment_aggregates_comment();
+
+-- comment score
+create function comment_aggregates_score()
+returns trigger language plpgsql
+as $$
+begin
+  IF (TG_OP = 'INSERT') THEN
+    update comment_aggregates ca
+    set score = score + NEW.score,
+    upvotes = case when NEW.score = 1 then upvotes + 1 else upvotes end,
+    downvotes = case when NEW.score = -1 then downvotes + 1 else downvotes end
+    where ca.comment_id = NEW.comment_id;
+
+  ELSIF (TG_OP = 'DELETE') THEN
+    -- Join to comment because that comment may not exist anymore
+    update comment_aggregates ca
+    set score = score - OLD.score,
+    upvotes = case when OLD.score = 1 then upvotes - 1 else upvotes end,
+    downvotes = case when OLD.score = -1 then downvotes - 1 else downvotes end
+    from comment c
+    where ca.comment_id = c.id
+    and ca.comment_id = OLD.comment_id;
+
+  END IF;
+  return null;
+end $$;
+
+create trigger comment_aggregates_score
+after insert or delete on comment_like
+for each row
+execute procedure comment_aggregates_score();
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..7a4801f403dd014f28a2b2606d39cdf4dfc4bdcb 100644 (file)
@@ -4,12 +4,13 @@ use chrono::{DateTime, NaiveDateTime, Utc};
 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_,
-  user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  source::{community::Community, user::User_},
+  views::{
+    comment_view::{CommentQueryBuilder, CommentView},
+    post_view::{PostQueryBuilder, PostView},
+    site_view::SiteView,
+    user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+  },
   ListingType,
   SortType,
 };
@@ -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);
   }
 
@@ -167,7 +168,7 @@ fn get_feed_user(
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::All)
     .sort(sort_type)
-    .for_creator_id(user.id)
+    .creator_id(user.id)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -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);
 
@@ -193,7 +194,7 @@ fn get_feed_community(
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::All)
     .sort(sort_type)
-    .for_community_id(community.id)
+    .community_id(community.id)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -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);
 
@@ -222,8 +223,8 @@ fn get_feed_front(
 
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(&ListingType::Subscribed)
-    .sort(sort_type)
     .my_user_id(user_id)
+    .sort(sort_type)
     .list()?;
 
   let items = create_post_items(posts)?;
@@ -231,11 +232,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);
   }
 
@@ -248,11 +249,15 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
 
   let sort = SortType::New;
 
-  let replies = ReplyQueryBuilder::create(&conn, user_id)
+  let replies = CommentQueryBuilder::create(&conn)
+    .recipient_id(user_id)
+    .my_user_id(user_id)
     .sort(&sort)
     .list()?;
 
-  let mentions = UserMentionQueryBuilder::create(&conn, user_id)
+  let mentions = UserMentionQueryBuilder::create(&conn)
+    .recipient_id(user_id)
+    .my_user_id(user_id)
     .sort(&sort)
     .list()?;
 
@@ -261,14 +266,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);
   }
 
@@ -276,7 +281,7 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Le
 }
 
 fn create_reply_and_mention_items(
-  replies: Vec<ReplyView>,
+  replies: Vec<CommentView>,
   mentions: Vec<UserMentionView>,
 ) -> Result<Vec<Item>, LemmyError> {
   let mut reply_items: Vec<Item> = replies
@@ -285,10 +290,15 @@ fn create_reply_and_mention_items(
       let reply_url = format!(
         "{}/post/{}/comment/{}",
         Settings::get().get_protocol_and_hostname(),
-        r.post_id,
-        r.id
+        r.post.id,
+        r.comment.id
       );
-      build_item(&r.creator_name, &r.published, &reply_url, &r.content)
+      build_item(
+        &r.creator.name,
+        &r.comment.published,
+        &reply_url,
+        &r.comment.content,
+      )
     })
     .collect::<Result<Vec<Item>, LemmyError>>()?;
 
@@ -298,10 +308,15 @@ fn create_reply_and_mention_items(
       let mention_url = format!(
         "{}/post/{}/comment/{}",
         Settings::get().get_protocol_and_hostname(),
-        m.post_id,
-        m.id
+        m.post.id,
+        m.comment.id
       );
-      build_item(&m.creator_name, &m.published, &mention_url, &m.content)
+      build_item(
+        &m.creator.name,
+        &m.comment.published,
+        &mention_url,
+        &m.comment.content,
+      )
     })
     .collect::<Result<Vec<Item>, LemmyError>>()?;
 
@@ -349,17 +364,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 +387,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,