From 48f187188bce9f5fa1ac8ee09615540ee4df8540 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 28 Feb 2023 22:46:15 -0500 Subject: [PATCH] Show deleted and removed posts for profile views. Fixes #2624 (#2729) * Show deleted and removed posts for profile views. Fixes #2624 * Only showing non-deleted/removed posts for creator. * Add a admin or mod check to views, to show deleted and removed posts. - Also removed the pointless "blanking" functions * Fix clippy * Make hidden posts comment clearer. * Fixing federation tests. * Fixing fmt. --- api_tests/src/comment.spec.ts | 2 - api_tests/src/post.spec.ts | 4 +- api_tests/src/private_message.spec.ts | 1 - api_tests/yarn.lock | 8 +- crates/api/src/community/block.rs | 3 +- crates/api/src/community/follow.rs | 3 +- crates/api/src/community/transfer.rs | 2 +- crates/api/src/post/mark_read.rs | 2 +- crates/api/src/post/save.rs | 2 +- crates/api/src/post_report/create.rs | 2 +- crates/api_common/src/utils.rs | 17 +++ crates/api_common/src/websocket/send.rs | 20 +-- crates/api_crud/src/community/create.rs | 2 +- crates/api_crud/src/community/list.rs | 20 +-- crates/api_crud/src/post/read.rs | 45 ++++--- crates/api_crud/src/private_message/read.rs | 12 -- .../apub/src/activities/following/accept.rs | 3 +- crates/apub/src/api/list_comments.rs | 12 +- crates/apub/src/api/list_posts.rs | 33 ++--- crates/apub/src/api/read_community.rs | 23 ++-- crates/apub/src/api/read_person.rs | 4 +- crates/apub/src/api/resolve_object.rs | 4 +- crates/apub/src/api/search.rs | 35 ++---- crates/db_schema/src/impls/comment.rs | 9 +- crates/db_schema/src/impls/community.rs | 23 +--- crates/db_schema/src/impls/post.rs | 16 +-- crates/db_schema/src/impls/private_message.rs | 9 +- crates/db_schema/src/traits.rs | 5 - crates/db_views/src/post_view.rs | 114 +++++++++++++----- crates/db_views_actor/src/community_view.rs | 23 +++- 30 files changed, 215 insertions(+), 243 deletions(-) diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 01b6efd52..a951c1ee0 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -120,7 +120,6 @@ test("Delete a comment", async () => { commentRes.comment_view.comment.id ); expect(deleteCommentRes.comment_view.comment.deleted).toBe(true); - expect(deleteCommentRes.comment_view.comment.content).toBe(""); // Make sure that comment is undefined on beta let betaCommentRes = (await resolveComment( @@ -159,7 +158,6 @@ test("Remove a comment from admin and community on the same instance", async () // The beta admin removes it (the community lives on beta) let removeCommentRes = await removeComment(beta, true, betaCommentId); expect(removeCommentRes.comment_view.comment.removed).toBe(true); - expect(removeCommentRes.comment_view.comment.content).toBe(""); // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it) let refetchedPostComments = await getComments( diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index 5aac0f511..24ce44b84 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -388,7 +388,7 @@ test("Enforce site ban for federated user", async () => { // existing alpha post should be removed on beta let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post); - expect(searchBeta2.posts[0]).toBeUndefined(); + expect(searchBeta2.posts[0].post.removed).toBe(true); // Unban alpha let unBanAlpha = await banPersonFromSite( @@ -436,7 +436,7 @@ test("Enforce community ban for federated user", async () => { // ensure that the post by alpha got removed let searchAlpha1 = await searchPostLocal(alpha, postRes1.post_view.post); - expect(searchAlpha1.posts[0]).toBeUndefined(); + expect(searchAlpha1.posts[0].post.removed).toBe(true); // Alpha tries to make post on beta, but it fails because of ban let postRes2 = await createPost(alpha, betaCommunity.community.id); diff --git a/api_tests/src/private_message.spec.ts b/api_tests/src/private_message.spec.ts index 751f72230..045805a6b 100644 --- a/api_tests/src/private_message.spec.ts +++ b/api_tests/src/private_message.spec.ts @@ -64,7 +64,6 @@ test("Delete a private message", async () => { pmRes.private_message_view.private_message.id ); expect(deletedPmRes.private_message_view.private_message.deleted).toBe(true); - expect(deletedPmRes.private_message_view.private_message.content).toBe(""); // The GetPrivateMessages filters out deleted, // even though they are in the actual database. diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 3701e2f1a..1a1d7979f 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2363,10 +2363,10 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lemmy-js-client@0.17.0-rc.61: - version "0.17.0-rc.61" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.61.tgz#c01e129a3d4c3483ecf337f1e4acf0ad91f9684f" - integrity sha512-xauBCD5i4vlUEWqsTMIXLCXeIjAK7ivVIN3C/g+RMAM7mD3CTcRkDZUerwnvLipIfr7V/4iYLWZW0orBaiV1CQ== +lemmy-js-client@0.17.2-rc.1: + version "0.17.2-rc.1" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.1.tgz#fe8d1508311bbf245acc98c2c3e47e2165a95b14" + integrity sha512-YrOXuCofgkqp28krmPTQZAfUWL5zEDA0sRJ0abKcgf/I8YYkYkUkPS9TOORN5Lv3bc8RAAz4+2/zLHqYL/Tnow== dependencies: node-fetch "2.6.6" diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index 914b5238e..58dabf1c0 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -57,7 +57,8 @@ impl Perform for BlockCommunity { .map_err(|e| LemmyError::from_error_message(e, "community_block_already_exists"))?; } - let community_view = CommunityView::read(context.pool(), community_id, Some(person_id)).await?; + let community_view = + CommunityView::read(context.pool(), community_id, Some(person_id), None).await?; Ok(BlockCommunityResponse { blocked: data.block, diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index b0729bdc6..b519d214b 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -53,7 +53,8 @@ impl Perform for FollowCommunity { let community_id = data.community_id; let person_id = local_user_view.person.id; - let community_view = CommunityView::read(context.pool(), community_id, Some(person_id)).await?; + let community_view = + CommunityView::read(context.pool(), community_id, Some(person_id), None).await?; let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?; Ok(Self::Response { diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index e46ed3518..c163b1950 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -83,7 +83,7 @@ impl Perform for TransferCommunity { let community_id = data.community_id; let person_id = local_user_view.person.id; - let community_view = CommunityView::read(context.pool(), community_id, Some(person_id)) + let community_view = CommunityView::read(context.pool(), community_id, Some(person_id), None) .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index 9fdf01b12..858d71d22 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -33,7 +33,7 @@ impl Perform for MarkPostAsRead { } // Fetch it - let post_view = PostView::read(context.pool(), post_id, Some(person_id)).await?; + let post_view = PostView::read(context.pool(), post_id, Some(person_id), None).await?; let res = Self::Response { post_view }; diff --git a/crates/api/src/post/save.rs b/crates/api/src/post/save.rs index b36f844b6..e0c7c8168 100644 --- a/crates/api/src/post/save.rs +++ b/crates/api/src/post/save.rs @@ -43,7 +43,7 @@ impl Perform for SavePost { let post_id = data.post_id; let person_id = local_user_view.person.id; - let post_view = PostView::read(context.pool(), post_id, Some(person_id)).await?; + let post_view = PostView::read(context.pool(), post_id, Some(person_id), None).await?; // Mark the post as read mark_post_as_read(person_id, post_id, context.pool()).await?; diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index a297f22ae..9ada7b1d6 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -37,7 +37,7 @@ impl Perform for CreatePostReport { let person_id = local_user_view.person.id; let post_id = data.post_id; - let post_view = PostView::read(context.pool(), post_id, None).await?; + let post_view = PostView::read(context.pool(), post_id, None, None).await?; check_community_ban(person_id, post_view.community.id, context.pool()).await?; diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 722aacc4f..0058128c1 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -61,6 +61,23 @@ pub async fn is_mod_or_admin( Ok(()) } +#[tracing::instrument(skip_all)] +pub async fn is_mod_or_admin_opt( + pool: &DbPool, + local_user_view: Option<&LocalUserView>, + community_id: Option, +) -> Result<(), LemmyError> { + if let Some(local_user_view) = local_user_view { + if let Some(community_id) = community_id { + is_mod_or_admin(pool, local_user_view.person.id, community_id).await + } else { + is_admin(local_user_view) + } + } else { + Err(LemmyError::from_message("not_a_mod_or_admin")) + } +} + pub async fn is_top_admin(pool: &DbPool, person_id: PersonId) -> Result<(), LemmyError> { let admins = PersonViewSafe::admins(pool).await?; let top_admin = admins diff --git a/crates/api_common/src/websocket/send.rs b/crates/api_common/src/websocket/send.rs index 44d380d34..0e8a78a20 100644 --- a/crates/api_common/src/websocket/send.rs +++ b/crates/api_common/src/websocket/send.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ person_mention::{PersonMention, PersonMentionInsertForm}, post::Post, }, - traits::{Crud, DeleteableOrRemoveable}, + traits::Crud, SubscribedType, }; use lemmy_db_views::structs::{CommentView, LocalUserView, PostView, PrivateMessageView}; @@ -32,7 +32,7 @@ pub async fn send_post_ws_message person_id: Option, context: &LemmyContext, ) -> Result { - let post_view = PostView::read(context.pool(), post_id, person_id).await?; + let post_view = PostView::read(context.pool(), post_id, person_id, Some(true)).await?; let res = PostResponse { post_view }; @@ -65,11 +65,7 @@ pub async fn send_comment_ws_message, context: &LemmyContext, ) -> Result { - let mut view = CommentView::read(context.pool(), comment_id, person_id).await?; - - if view.comment.deleted || view.comment.removed { - view.comment = view.comment.blank_out_deleted_or_removed_info(); - } + let view = CommentView::read(context.pool(), comment_id, person_id).await?; let mut res = CommentResponse { comment_view: view, @@ -98,7 +94,8 @@ pub async fn send_community_ws_message, context: &LemmyContext, ) -> Result { - let community_view = CommunityView::read(context.pool(), community_id, person_id).await?; + let community_view = + CommunityView::read(context.pool(), community_id, person_id, Some(true)).await?; let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?; let mut res = CommunityResponse { @@ -124,12 +121,7 @@ pub async fn send_pm_ws_message( websocket_id: Option, context: &LemmyContext, ) -> Result { - let mut view = PrivateMessageView::read(context.pool(), private_message_id).await?; - - // Blank out deleted or removed info - if view.private_message.deleted { - view.private_message = view.private_message.blank_out_deleted_or_removed_info(); - } + let view = PrivateMessageView::read(context.pool(), private_message_id).await?; let res = PrivateMessageResponse { private_message_view: view, diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 1bf7ba800..7eb81067d 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -147,7 +147,7 @@ impl PerformCrud for CreateCommunity { let person_id = local_user_view.person.id; let community_view = - CommunityView::read(context.pool(), inserted_community.id, Some(person_id)).await?; + CommunityView::read(context.pool(), inserted_community.id, Some(person_id), None).await?; let discussion_languages = CommunityLanguage::read(context.pool(), inserted_community.id).await?; diff --git a/crates/api_crud/src/community/list.rs b/crates/api_crud/src/community/list.rs index 4d2e7046f..ef4c46d81 100644 --- a/crates/api_crud/src/community/list.rs +++ b/crates/api_crud/src/community/list.rs @@ -3,9 +3,9 @@ use actix_web::web::Data; use lemmy_api_common::{ community::{ListCommunities, ListCommunitiesResponse}, context::LemmyContext, - utils::{check_private_instance, get_local_user_view_from_jwt_opt}, + utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_admin}, }; -use lemmy_db_schema::{source::local_site::LocalSite, traits::DeleteableOrRemoveable}; +use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views_actor::community_view::CommunityQuery; use lemmy_utils::{error::LemmyError, ConnectionId}; @@ -24,37 +24,27 @@ impl PerformCrud for ListCommunities { get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret()) .await?; let local_site = LocalSite::read(context.pool()).await?; + let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); check_private_instance(&local_user_view, &local_site)?; - let person_id = local_user_view.clone().map(|l| l.person.id); - let sort = data.sort; let listing_type = data.type_; let page = data.page; let limit = data.limit; let local_user = local_user_view.map(|l| l.local_user); - let mut communities = CommunityQuery::builder() + let communities = CommunityQuery::builder() .pool(context.pool()) .listing_type(listing_type) .sort(sort) .local_user(local_user.as_ref()) .page(page) .limit(limit) + .is_mod_or_admin(is_admin) .build() .list() .await?; - // Blank out deleted or removed info for non-logged in users - if person_id.is_none() { - for cv in communities - .iter_mut() - .filter(|cv| cv.community.deleted || cv.community.removed) - { - cv.community = cv.clone().community.blank_out_deleted_or_removed_info(); - } - } - // Return the jwt Ok(ListCommunitiesResponse { communities }) } diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index 49bb3765d..a6612cbb4 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -3,12 +3,17 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, post::{GetPost, GetPostResponse}, - utils::{check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read}, + utils::{ + check_private_instance, + get_local_user_view_from_jwt_opt, + is_mod_or_admin_opt, + mark_post_as_read, + }, }; use lemmy_db_schema::{ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, - source::{comment::Comment, local_site::LocalSite}, - traits::{Crud, DeleteableOrRemoveable}, + source::{comment::Comment, local_site::LocalSite, post::Post}, + traits::Crud, }; use lemmy_db_views::structs::PostView; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; @@ -32,7 +37,7 @@ impl PerformCrud for GetPost { check_private_instance(&local_user_view, &local_site)?; - let person_id = local_user_view.map(|u| u.person.id); + let person_id = local_user_view.as_ref().map(|u| u.person.id); // I'd prefer fetching the post_view by a comment join, but it adds a lot of boilerplate let post_id = if let Some(id) = data.id { @@ -46,7 +51,14 @@ impl PerformCrud for GetPost { Err(LemmyError::from_message("couldnt_find_post"))? }; - let mut post_view = PostView::read(context.pool(), post_id, person_id) + // Check to see if the person is a mod or admin, to show deleted / removed + let community_id = Post::read(context.pool(), post_id).await?.community_id; + let is_mod_or_admin = + is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), Some(community_id)) + .await + .is_ok(); + + let post_view = PostView::read(context.pool(), post_id, person_id, Some(is_mod_or_admin)) .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?; @@ -57,10 +69,14 @@ impl PerformCrud for GetPost { } // Necessary for the sidebar subscribed - let community_id = post_view.community.id; - let mut community_view = CommunityView::read(context.pool(), community_id, person_id) - .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + let community_view = CommunityView::read( + context.pool(), + community_id, + person_id, + Some(is_mod_or_admin), + ) + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; // Insert into PersonPostAggregates // to update the read_comments count @@ -77,17 +93,6 @@ impl PerformCrud for GetPost { .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?; } - // Blank out deleted or removed info for non-logged in users - if person_id.is_none() { - if post_view.post.deleted || post_view.post.removed { - post_view.post = post_view.post.blank_out_deleted_or_removed_info(); - } - - if community_view.community.deleted || community_view.community.removed { - community_view.community = community_view.community.blank_out_deleted_or_removed_info(); - } - } - let moderators = CommunityModeratorView::for_community(context.pool(), community_id).await?; let online = context.chat_server().get_post_users_online(post_id)?; diff --git a/crates/api_crud/src/private_message/read.rs b/crates/api_crud/src/private_message/read.rs index 286802745..c9a377533 100644 --- a/crates/api_crud/src/private_message/read.rs +++ b/crates/api_crud/src/private_message/read.rs @@ -5,7 +5,6 @@ use lemmy_api_common::{ private_message::{GetPrivateMessages, PrivateMessagesResponse}, utils::get_local_user_view_from_jwt, }; -use lemmy_db_schema::traits::DeleteableOrRemoveable; use lemmy_db_views::private_message_view::PrivateMessageQuery; use lemmy_utils::{error::LemmyError, ConnectionId}; @@ -45,17 +44,6 @@ impl PerformCrud for GetPrivateMessages { } }); - // Blank out deleted or removed info - for pmv in messages - .iter_mut() - .filter(|pmv| pmv.private_message.deleted) - { - pmv.private_message = pmv - .clone() - .private_message - .blank_out_deleted_or_removed_info(); - } - Ok(PrivateMessagesResponse { private_messages: messages, }) diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index 08853c50d..bd3e5cad2 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -99,7 +99,8 @@ impl ActivityHandler for AcceptFollow { // Send the Subscribed message over websocket // Re-read the community_view to get the new SubscribedType - let community_view = CommunityView::read(context.pool(), community_id, Some(person_id)).await?; + let community_view = + CommunityView::read(context.pool(), community_id, Some(person_id), None).await?; // Get the local_user_id let local_recipient_id = LocalUserView::read_person(context.pool(), person_id) diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index c4af6900a..3d44baf0e 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -15,7 +15,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{comment::Comment, community::Community, local_site::LocalSite}, - traits::{Crud, DeleteableOrRemoveable}, + traits::Crud, }; use lemmy_db_views::comment_view::CommentQuery; use lemmy_utils::{error::LemmyError, ConnectionId}; @@ -65,7 +65,7 @@ impl PerformApub for GetComments { let parent_path_cloned = parent_path.clone(); let post_id = data.post_id; let local_user = local_user_view.map(|l| l.local_user); - let mut comments = CommentQuery::builder() + let comments = CommentQuery::builder() .pool(context.pool()) .listing_type(Some(listing_type)) .sort(sort) @@ -83,14 +83,6 @@ impl PerformApub for GetComments { .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?; - // Blank out deleted or removed info - for cv in comments - .iter_mut() - .filter(|cv| cv.comment.deleted || cv.comment.removed) - { - cv.comment = cv.clone().comment.blank_out_deleted_or_removed_info(); - } - Ok(GetCommentsResponse { comments }) } } diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index 7a1f815c7..2908c4ba7 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -10,13 +10,11 @@ use lemmy_api_common::{ utils::{ check_private_instance, get_local_user_view_from_jwt_opt, + is_mod_or_admin_opt, listing_type_with_site_default, }, }; -use lemmy_db_schema::{ - source::{community::Community, local_site::LocalSite}, - traits::DeleteableOrRemoveable, -}; +use lemmy_db_schema::source::{community::Community, local_site::LocalSite}; use lemmy_db_views::post_view::PostQuery; use lemmy_utils::{error::LemmyError, ConnectionId}; @@ -38,8 +36,6 @@ impl PerformApub for GetPosts { check_private_instance(&local_user_view, &local_site)?; - let is_logged_in = local_user_view.is_some(); - let sort = data.sort; let listing_type = listing_type_with_site_default(data.type_, &local_site)?; @@ -56,7 +52,12 @@ impl PerformApub for GetPosts { }; let saved_only = data.saved_only; - let mut posts = PostQuery::builder() + let is_mod_or_admin = + is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), community_id) + .await + .is_ok(); + + let posts = PostQuery::builder() .pool(context.pool()) .local_user(local_user_view.map(|l| l.local_user).as_ref()) .listing_type(Some(listing_type)) @@ -66,28 +67,12 @@ impl PerformApub for GetPosts { .saved_only(saved_only) .page(page) .limit(limit) + .is_mod_or_admin(Some(is_mod_or_admin)) .build() .list() .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?; - // Blank out deleted or removed info for non-logged in users - if !is_logged_in { - for pv in posts - .iter_mut() - .filter(|p| p.post.deleted || p.post.removed) - { - pv.post = pv.clone().post.blank_out_deleted_or_removed_info(); - } - - for pv in posts - .iter_mut() - .filter(|p| p.community.deleted || p.community.removed) - { - pv.community = pv.clone().community.blank_out_deleted_or_removed_info(); - } - } - Ok(GetPostsResponse { posts }) } } diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs index 8ffa48ba7..738c8a3f1 100644 --- a/crates/apub/src/api/read_community.rs +++ b/crates/apub/src/api/read_community.rs @@ -7,7 +7,7 @@ use actix_web::web::Data; use lemmy_api_common::{ community::{GetCommunity, GetCommunityResponse}, context::LemmyContext, - utils::{check_private_instance, get_local_user_view_from_jwt_opt}, + utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_mod_or_admin_opt}, }; use lemmy_db_schema::{ impls::actor_language::default_post_language, @@ -17,7 +17,6 @@ use lemmy_db_schema::{ local_site::LocalSite, site::Site, }, - traits::DeleteableOrRemoveable, }; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use lemmy_utils::{error::LemmyError, ConnectionId}; @@ -57,15 +56,19 @@ impl PerformApub for GetCommunity { } }; - let mut community_view = CommunityView::read(context.pool(), community_id, person_id) - .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; + let is_mod_or_admin = + is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), Some(community_id)) + .await + .is_ok(); - // Blank out deleted or removed info for non-logged in users - if person_id.is_none() && (community_view.community.deleted || community_view.community.removed) - { - community_view.community = community_view.community.blank_out_deleted_or_removed_info(); - } + let community_view = CommunityView::read( + context.pool(), + community_id, + person_id, + Some(is_mod_or_admin), + ) + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; let moderators = CommunityModeratorView::for_community(context.pool(), community_id) .await diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index 514bcfccc..c73d710fe 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, person::{GetPersonDetails, GetPersonDetailsResponse}, - utils::{check_private_instance, get_local_user_view_from_jwt_opt}, + utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_admin}, }; use lemmy_db_schema::{ source::{local_site::LocalSite, person::Person}, @@ -34,6 +34,7 @@ impl PerformApub for GetPersonDetails { get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret()) .await?; let local_site = LocalSite::read(context.pool()).await?; + let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); check_private_instance(&local_user_view, &local_site)?; @@ -71,6 +72,7 @@ impl PerformApub for GetPersonDetails { .saved_only(saved_only) .local_user(local_user.as_ref()) .community_id(community_id) + .is_mod_or_admin(is_admin) .page(page) .limit(limit); diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index c4fb2e9b8..537e1adb3 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -56,11 +56,11 @@ async fn convert_response( } Community(c) => { removed_or_deleted = c.deleted || c.removed; - res.community = Some(CommunityView::read(pool, c.id, user_id).await?) + res.community = Some(CommunityView::read(pool, c.id, user_id, None).await?) } Post(p) => { removed_or_deleted = p.deleted || p.removed; - res.post = Some(PostView::read(pool, p.id, user_id).await?) + res.post = Some(PostView::read(pool, p.id, user_id, None).await?) } Comment(c) => { removed_or_deleted = c.deleted || c.removed; diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index cad41c549..dee8c8027 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -7,11 +7,10 @@ use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, site::{Search, SearchResponse}, - utils::{check_private_instance, get_local_user_view_from_jwt_opt}, + utils::{check_private_instance, get_local_user_view_from_jwt_opt, is_admin}, }; use lemmy_db_schema::{ source::{community::Community, local_site::LocalSite}, - traits::DeleteableOrRemoveable, utils::post_to_comment_sort_type, SearchType, }; @@ -38,7 +37,8 @@ impl PerformApub for Search { check_private_instance(&local_user_view, &local_site)?; - let person_id = local_user_view.as_ref().map(|u| u.person.id); + let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); + let local_user = local_user_view.map(|l| l.local_user); let mut posts = Vec::new(); @@ -75,6 +75,7 @@ impl PerformApub for Search { .creator_id(creator_id) .local_user(local_user.as_ref()) .search_term(Some(q)) + .is_mod_or_admin(is_admin) .page(page) .limit(limit) .build() @@ -104,6 +105,7 @@ impl PerformApub for Search { .listing_type(listing_type) .search_term(Some(q)) .local_user(local_user.as_ref()) + .is_mod_or_admin(is_admin) .page(page) .limit(limit) .build() @@ -137,6 +139,7 @@ impl PerformApub for Search { .creator_id(creator_id) .local_user(local_user_.as_ref()) .search_term(Some(q)) + .is_mod_or_admin(is_admin) .page(page) .limit(limit) .build() @@ -173,6 +176,7 @@ impl PerformApub for Search { .listing_type(listing_type) .search_term(Some(q)) .local_user(local_user.as_ref()) + .is_mod_or_admin(is_admin) .page(page) .limit(limit) .build() @@ -205,6 +209,7 @@ impl PerformApub for Search { .community_actor_id(community_actor_id) .creator_id(creator_id) .url_search(Some(q)) + .is_mod_or_admin(is_admin) .page(page) .limit(limit) .build() @@ -213,30 +218,6 @@ impl PerformApub for Search { } }; - // Blank out deleted or removed info for non logged in users - if person_id.is_none() { - for cv in communities - .iter_mut() - .filter(|cv| cv.community.deleted || cv.community.removed) - { - cv.community = cv.clone().community.blank_out_deleted_or_removed_info(); - } - - for pv in posts - .iter_mut() - .filter(|p| p.post.deleted || p.post.removed) - { - pv.post = pv.clone().post.blank_out_deleted_or_removed_info(); - } - - for cv in comments - .iter_mut() - .filter(|cv| cv.comment.deleted || cv.comment.removed) - { - cv.comment = cv.clone().comment.blank_out_deleted_or_removed_info(); - } - } - // Return the jwt Ok(SearchResponse { type_: search_type.to_string(), diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 2a9eeafed..46045cd10 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -10,7 +10,7 @@ use crate::{ CommentSavedForm, CommentUpdateForm, }, - traits::{Crud, DeleteableOrRemoveable, Likeable, Saveable}, + traits::{Crud, Likeable, Saveable}, utils::{get_conn, naive_now, DbPool}, }; use diesel::{ @@ -240,13 +240,6 @@ impl Saveable for CommentSaved { } } -impl DeleteableOrRemoveable for Comment { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.content = String::new(); - self - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index f341b5ed9..d4dd2ebc7 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -12,11 +12,10 @@ use crate::{ CommunityModeratorForm, CommunityPersonBan, CommunityPersonBanForm, - CommunitySafe, CommunityUpdateForm, }, }, - traits::{ApubActor, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable}, + traits::{ApubActor, Bannable, Crud, Followable, Joinable}, utils::{functions::lower, get_conn, DbPool}, SubscribedType, }; @@ -174,26 +173,6 @@ impl Joinable for CommunityModerator { } } -impl DeleteableOrRemoveable for CommunitySafe { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.title = String::new(); - self.description = None; - self.icon = None; - self.banner = None; - self - } -} - -impl DeleteableOrRemoveable for Community { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.title = String::new(); - self.description = None; - self.icon = None; - self.banner = None; - self - } -} - pub enum CollectionType { Moderators, Featured, diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 8c5ffa8ad..a3309428c 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -26,7 +26,7 @@ use crate::{ PostSavedForm, PostUpdateForm, }, - traits::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable}, + traits::{Crud, Likeable, Readable, Saveable}, utils::{get_conn, naive_now, DbPool, FETCH_LIMIT_MAX}, }; use ::url::Url; @@ -317,20 +317,6 @@ impl Readable for PostRead { } } -impl DeleteableOrRemoveable for Post { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.name = String::new(); - self.url = None; - self.body = None; - self.embed_title = None; - self.embed_description = None; - self.embed_video_url = None; - self.thumbnail_url = None; - - self - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index a30ac20e5..cf6782270 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -2,7 +2,7 @@ use crate::{ newtypes::{DbUrl, PersonId, PrivateMessageId}, schema::private_message::dsl::{ap_id, private_message, read, recipient_id}, source::private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm}, - traits::{Crud, DeleteableOrRemoveable}, + traits::Crud, utils::{get_conn, DbPool}, }; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; @@ -86,13 +86,6 @@ impl PrivateMessage { } } -impl DeleteableOrRemoveable for PrivateMessage { - fn blank_out_deleted_or_removed_info(mut self) -> Self { - self.content = String::new(); - self - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 2055ab644..adbbf8026 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -140,11 +140,6 @@ pub trait Reportable { Self: Sized; } -// TODO these should be removed, there should be another way to do this -pub trait DeleteableOrRemoveable { - fn blank_out_deleted_or_removed_info(self) -> Self; -} - pub trait ToSafe { type SafeColumns; fn safe_columns_tuple() -> Self::SafeColumns; diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 1427d9483..ed2711c44 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -68,24 +68,14 @@ impl PostView { pool: &DbPool, post_id: PostId, my_person_id: Option, + is_mod_or_admin: Option, ) -> Result { let conn = &mut get_conn(pool).await?; // The left join below will return None in this case let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - let ( - post, - creator, - community, - creator_banned_from_community, - counts, - follower, - saved, - read, - creator_blocked, - post_like, - unread_comments, - ) = post::table + let person_alias_1 = diesel::alias!(person as person1); + let mut query = post::table .find(post_id) .inner_join(person::table) .inner_join(community::table) @@ -144,6 +134,14 @@ impl PostView { .and(person_post_aggregates::person_id.eq(person_id_join)), ), ) + // Used to check if you are the post creator + .left_join( + person_alias_1.on( + post::creator_id + .eq(person_alias_1.field(person::id)) + .and(person_alias_1.field(person::id).eq(person_id_join)), + ), + ) .select(( post::all_columns, Person::safe_columns_tuple(), @@ -160,8 +158,38 @@ impl PostView { post_aggregates::comments, ), )) - .first::(conn) - .await?; + .into_boxed(); + + // If you are not a moderator, exclude deleted or removed content + if !is_mod_or_admin.unwrap_or(true) { + // If you are not the creator, then remove the other fields. + query = query + .filter( + person_alias_1.field(person::id).is_null().and( + post::removed + .eq(false) + .and(post::deleted.eq(false)) + .and(community::removed.eq(false)) + .and(community::deleted.eq(false)), + ), + ) + // If you are the creator, keep them + .or_filter(person_alias_1.field(person::id).is_not_null()) + } + + let ( + post, + creator, + community, + creator_banned_from_community, + counts, + follower, + saved, + read, + creator_blocked, + post_like, + unread_comments, + ) = query.first::(conn).await?; // If a person is given, then my_vote, if None, should be 0, not null // Necessary to differentiate between other person's votes @@ -201,6 +229,8 @@ pub struct PostQuery<'a> { search_term: Option, url_search: Option, saved_only: Option, + /// Used to show deleted or removed posts for admins + is_mod_or_admin: Option, page: Option, limit: Option, } @@ -212,6 +242,7 @@ impl<'a> PostQuery<'a> { // The left join below will return None in this case let person_id_join = self.local_user.map(|l| l.person_id).unwrap_or(PersonId(-1)); let local_user_id_join = self.local_user.map(|l| l.id).unwrap_or(LocalUserId(-1)); + let person_alias_1 = diesel::alias!(person as person1); let mut query = post::table .inner_join(person::table) @@ -285,6 +316,14 @@ impl<'a> PostQuery<'a> { .and(local_user_language::local_user_id.eq(local_user_id_join)), ), ) + // Used to check if you are the post creator + .left_join( + person_alias_1.on( + post::creator_id + .eq(person_alias_1.field(person::id)) + .and(person_alias_1.field(person::id).eq(person_id_join)), + ), + ) .select(( post::all_columns, Person::safe_columns_tuple(), @@ -303,6 +342,23 @@ impl<'a> PostQuery<'a> { )) .into_boxed(); + // If you are not a moderator, exclude deleted or removed content + if !self.is_mod_or_admin.unwrap_or(true) { + // If you are not the creator, then remove the other fields. + query = query + .filter( + person_alias_1.field(person::id).is_null().and( + post::removed + .eq(false) + .and(post::deleted.eq(false)) + .and(community::removed.eq(false)) + .and(community::deleted.eq(false)), + ), + ) + // If you are the creator, keep them + .or_filter(person_alias_1.field(person::id).is_not_null()) + } + if let Some(listing_type) = self.listing_type { match listing_type { ListingType::Subscribed => { @@ -349,7 +405,6 @@ impl<'a> PostQuery<'a> { ); } - // If its for a specific person, show the removed / deleted if let Some(creator_id) = self.creator_id { query = query.filter(post::creator_id.eq(creator_id)); } @@ -424,13 +479,7 @@ impl<'a> PostQuery<'a> { let (limit, offset) = limit_and_offset(self.page, self.limit)?; - query = 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)); + query = query.limit(limit).offset(offset); debug!("Post View Query: {:?}", debug_query::(&query)); @@ -615,10 +664,14 @@ mod tests { .await .unwrap(); - let post_listing_single_with_person = - PostView::read(pool, data.inserted_post.id, Some(data.inserted_person.id)) - .await - .unwrap(); + let post_listing_single_with_person = PostView::read( + pool, + data.inserted_post.id, + Some(data.inserted_person.id), + None, + ) + .await + .unwrap(); let mut expected_post_listing_with_user = expected_post_view(&data, pool).await; @@ -670,9 +723,10 @@ mod tests { .await .unwrap(); - let read_post_listing_single_no_person = PostView::read(pool, data.inserted_post.id, None) - .await - .unwrap(); + let read_post_listing_single_no_person = + PostView::read(pool, data.inserted_post.id, None, None) + .await + .unwrap(); let expected_post_listing_no_person = expected_post_view(&data, pool).await; diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index e59025a5f..ff30d5dc5 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -37,12 +37,13 @@ impl CommunityView { pool: &DbPool, community_id: CommunityId, my_person_id: Option, + is_mod_or_admin: Option, ) -> Result { let conn = &mut get_conn(pool).await?; // The left join below will return None in this case let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - let (community, counts, follower, blocked) = community::table + let mut query = community::table .find(community_id) .inner_join(community_aggregates::table) .left_join( @@ -65,8 +66,16 @@ impl CommunityView { community_follower::all_columns.nullable(), community_block::all_columns.nullable(), )) - .first::(conn) - .await?; + .into_boxed(); + + // Hide deleted and removed for non-admins or mods + if !is_mod_or_admin.unwrap_or(true) { + query = query + .filter(community::removed.eq(false)) + .filter(community::deleted.eq(false)); + } + + let (community, counts, follower, blocked) = query.first::(conn).await?; Ok(CommunityView { community, @@ -116,6 +125,7 @@ pub struct CommunityQuery<'a> { sort: Option, local_user: Option<&'a LocalUser>, search_term: Option, + is_mod_or_admin: Option, page: Option, limit: Option, } @@ -159,6 +169,13 @@ impl<'a> CommunityQuery<'a> { .or_filter(community::title.ilike(searcher)); }; + // Hide deleted and removed for non-admins or mods + if !self.is_mod_or_admin.unwrap_or(true) { + query = query + .filter(community::removed.eq(false)) + .filter(community::deleted.eq(false)); + } + match self.sort.unwrap_or(SortType::Hot) { SortType::New => query = query.order_by(community::published.desc()), SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),