From 047db9ac8590670538129b5f765f47e7c66a8a80 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Thu, 20 Jul 2023 16:36:16 +0200 Subject: [PATCH] Handle displaying of deleted and removed posts/comments (fixes #2624) (#3286) * Handle displaying of deleted and removed posts/comments (fixes #2624) * remove duplicate test * fix tests * no show_removed/show_deleted * merge * partially fix tests * fix tests * clippy * fix tests * get rid of build_post_response_deleted_allowed --- api_tests/src/comment.spec.ts | 6 +- api_tests/src/post.spec.ts | 8 +- api_tests/src/shared.ts | 12 ++ crates/api_common/src/build_response.rs | 13 -- crates/api_crud/src/post/delete.rs | 4 +- crates/api_crud/src/post/remove.rs | 4 +- crates/apub/src/api/list_comments.rs | 3 +- crates/apub/src/api/list_posts.rs | 11 +- crates/apub/src/api/read_person.rs | 39 ++--- crates/apub/src/api/search.rs | 17 +- crates/db_views/src/comment_view.rs | 120 +++++++++----- crates/db_views/src/post_view.rs | 210 +++++++++++++++++------- crates/routes/src/feeds.rs | 4 +- 13 files changed, 273 insertions(+), 178 deletions(-) diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 80cb868f2..d1b20d77e 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -29,6 +29,7 @@ import { getComments, getCommentParentId, resolveCommunity, + getPersonDetails, } from "./shared"; import { CommentView } from "lemmy-js-client/dist/types/CommentView"; @@ -160,10 +161,11 @@ test("Remove a comment from admin and community on the same instance", async () expect(removeCommentRes.comment_view.comment.removed).toBe(true); // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it) - let refetchedPostComments = await getComments( + let refetchedPostComments = await getPersonDetails( alpha, - postRes.post_view.post.id, + commentRes.comment_view.comment.creator_id, ); + console.log(refetchedPostComments.comments[0].comment); expect(refetchedPostComments.comments[0].comment.removed).toBe(true); let unremoveCommentRes = await removeComment(beta, false, betaCommentId); diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index cabbcfd8a..8c48dc874 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -388,8 +388,8 @@ test("Enforce site ban for federated user", async () => { expect(alphaUserOnBeta1.person?.person.banned).toBe(true); // existing alpha post should be removed on beta - let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post); - expect(searchBeta2.posts[0].post.removed).toBe(true); + let searchBeta2 = await getPost(beta, searchBeta1.posts[0].post.id); + expect(searchBeta2.post_view.post.removed).toBe(true); // Unban alpha let unBanAlpha = await banPersonFromSite( @@ -436,8 +436,8 @@ test("Enforce community ban for federated user", async () => { expect(banAlpha.banned).toBe(true); // ensure that the post by alpha got removed - let searchAlpha1 = await searchPostLocal(alpha, postRes1.post_view.post); - expect(searchAlpha1.posts[0].post.removed).toBe(true); + let searchAlpha1 = await getPost(alpha, searchBeta1.posts[0].post.id); + expect(searchAlpha1.post_view.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/shared.ts b/api_tests/src/shared.ts index 0523712e0..251f3ed96 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -58,6 +58,8 @@ import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportR import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport"; import { ListCommentReportsResponse } from "lemmy-js-client/dist/types/ListCommentReportsResponse"; import { ListCommentReports } from "lemmy-js-client/dist/types/ListCommentReports"; +import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse"; +import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails"; export interface API { client: LemmyHttp; @@ -646,6 +648,16 @@ export async function saveUserSettings( ): Promise { return api.client.saveUserSettings(form); } +export async function getPersonDetails( + api: API, + person_id: number, +): Promise { + let form: GetPersonDetails = { + auth: api.auth, + person_id: person_id, + }; + return api.client.getPersonDetails(form); +} export async function deleteUser(api: API): Promise { let form: DeleteAccount = { diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index a61da7f0b..8a63f7ad4 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -82,19 +82,6 @@ pub async fn build_post_response( Ok(PostResponse { post_view }) } -// this is a variation of build_post_response that returns post even if end-user-delted or mod-removed. -// Assumption is that before this function is ever called, the user is already confirmed to be authorized to view post. -// See GitHub Issue #3588 -pub async fn build_post_response_deleted_allowed( - context: &Data, - _community_id: CommunityId, - person_id: PersonId, - post_id: PostId, -) -> Result { - let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), Some(true)).await?; - Ok(PostResponse { post_view }) -} - // TODO: this function is a mess and should be split up to handle email seperately #[tracing::instrument(skip_all)] pub async fn send_local_notifs( diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index fa1c03108..eaeb66c43 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -1,7 +1,7 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{ - build_response::build_post_response_deleted_allowed, + build_response::build_post_response, context::LemmyContext, post::{DeletePost, PostResponse}, utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt}, @@ -52,7 +52,7 @@ impl PerformCrud for DeletePost { ) .await?; - build_post_response_deleted_allowed( + build_post_response( context, orig_post.community_id, local_user_view.person.id, diff --git a/crates/api_crud/src/post/remove.rs b/crates/api_crud/src/post/remove.rs index 8feff19fc..7950d5047 100644 --- a/crates/api_crud/src/post/remove.rs +++ b/crates/api_crud/src/post/remove.rs @@ -1,7 +1,7 @@ use crate::PerformCrud; use actix_web::web::Data; use lemmy_api_common::{ - build_response::build_post_response_deleted_allowed, + build_response::build_post_response, context::LemmyContext, post::{PostResponse, RemovePost}, utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt}, @@ -61,7 +61,7 @@ impl PerformCrud for RemovePost { }; ModRemovePost::create(&mut context.pool(), &form).await?; - build_post_response_deleted_allowed( + build_post_response( context, orig_post.community_id, local_user_view.person.id, diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index b64871221..f07ce3dad 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -54,7 +54,6 @@ pub async fn list_comments( 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 comments = CommentQuery { listing_type, sort, @@ -63,7 +62,7 @@ pub async fn list_comments( community_id, parent_path: parent_path_cloned, post_id, - local_user: local_user.as_ref(), + local_user: local_user_view.as_ref(), page, limit, ..Default::default() diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index c60abc966..2ebd6b766 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -8,7 +8,7 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPosts, GetPostsResponse}, - utils::{check_private_instance, is_mod_or_admin_opt, local_user_view_from_jwt_opt}, + utils::{check_private_instance, local_user_view_from_jwt_opt}, }; use lemmy_db_schema::source::{community::Community, local_site::LocalSite}; use lemmy_db_views::post_view::PostQuery; @@ -42,21 +42,14 @@ pub async fn list_posts( community_id, )?); - let is_mod_or_admin = Some( - is_mod_or_admin_opt(&mut context.pool(), local_user_view.as_ref(), community_id) - .await - .is_ok(), - ); - let posts = PostQuery { - local_user: local_user_view.map(|l| l.local_user).as_ref(), + local_user: local_user_view.as_ref(), listing_type, sort, community_id, saved_only, page, limit, - is_mod_or_admin, ..Default::default() } .list(&mut context.pool()) diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index 60cea7663..5d3c73c30 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -4,7 +4,7 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, person::{GetPersonDetails, GetPersonDetailsResponse}, - utils::{check_private_instance, is_admin, local_user_view_from_jwt_opt}, + utils::{check_private_instance, local_user_view_from_jwt_opt}, }; use lemmy_db_schema::{ source::{local_site::LocalSite, person::Person}, @@ -26,7 +26,6 @@ pub async fn read_person( let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; let local_site = LocalSite::read(&mut 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)?; @@ -53,48 +52,38 @@ pub async fn read_person( let limit = data.limit; let saved_only = data.saved_only; let community_id = data.community_id; - let local_user = local_user_view.map(|l| l.local_user); - let local_user_clone = local_user.clone(); + // 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 + let creator_id = if !saved_only.unwrap_or(false) { + Some(person_details_id) + } else { + None + }; let posts = PostQuery { sort, saved_only, - local_user:local_user.as_ref(), + local_user: local_user_view.as_ref(), community_id, - is_mod_or_admin: is_admin, + is_profile_view: Some(true), page, limit, - creator_id: - // 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.unwrap_or(false) { - Some(person_details_id) - } else { - None - } - , + creator_id, ..Default::default() } .list(&mut context.pool()) .await?; let comments = CommentQuery { - local_user: (local_user_clone.as_ref()), + local_user: (local_user_view.as_ref()), sort: (sort.map(post_to_comment_sort_type)), saved_only: (saved_only), show_deleted_and_removed: (Some(false)), community_id: (community_id), + is_profile_view: Some(true), page: (page), limit: (limit), - creator_id: ( - // 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.unwrap_or(false) { - Some(person_details_id) - } else { - None - } - ), + creator_id, ..Default::default() } .list(&mut context.pool()) diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 2c65f302d..ca84606ff 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -50,7 +50,7 @@ pub async fn search( data.community_id }; let creator_id = data.creator_id; - let local_user = local_user_view.map(|l| l.local_user); + let local_user = local_user_view.as_ref().map(|l| l.local_user.clone()); match search_type { SearchType::Posts => { posts = PostQuery { @@ -58,9 +58,8 @@ pub async fn search( listing_type: (listing_type), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user.as_ref()), + local_user: (local_user_view.as_ref()), search_term: (Some(q)), - is_mod_or_admin: (is_admin), page: (page), limit: (limit), ..Default::default() @@ -75,7 +74,7 @@ pub async fn search( search_term: (Some(q)), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user.as_ref()), + local_user: (local_user_view.as_ref()), page: (page), limit: (limit), ..Default::default() @@ -112,15 +111,15 @@ pub async fn search( let community_or_creator_included = data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); - let local_user_ = local_user.clone(); + let q = data.q.clone(); + posts = PostQuery { sort: (sort), listing_type: (listing_type), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_.as_ref()), + local_user: (local_user_view.as_ref()), search_term: (Some(q)), - is_mod_or_admin: (is_admin), page: (page), limit: (limit), ..Default::default() @@ -130,14 +129,13 @@ pub async fn search( let q = data.q.clone(); - let local_user_ = local_user.clone(); comments = CommentQuery { sort: (sort.map(post_to_comment_sort_type)), listing_type: (listing_type), search_term: (Some(q)), community_id: (community_id), creator_id: (creator_id), - local_user: (local_user_.as_ref()), + local_user: (local_user_view.as_ref()), page: (page), limit: (limit), ..Default::default() @@ -186,7 +184,6 @@ pub async fn search( community_id: (community_id), creator_id: (creator_id), url_search: (Some(q)), - is_mod_or_admin: (is_admin), page: (page), limit: (limit), ..Default::default() diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 9a9685c79..889adeeb7 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -1,4 +1,4 @@ -use crate::structs::CommentView; +use crate::structs::{CommentView, LocalUserView}; use diesel::{ result::Error, BoolExpressionMethods, @@ -30,7 +30,6 @@ use lemmy_db_schema::{ source::{ comment::{Comment, CommentSaved}, community::{Community, CommunityFollower, CommunityPersonBan}, - local_user::LocalUser, person::Person, person_block::PersonBlock, post::Post, @@ -163,9 +162,10 @@ pub struct CommentQuery<'a> { pub post_id: Option, pub parent_path: Option, pub creator_id: Option, - pub local_user: Option<&'a LocalUser>, + pub local_user: Option<&'a LocalUserView>, pub search_term: Option, pub saved_only: Option, + pub is_profile_view: Option, pub show_deleted_and_removed: Option, pub page: Option, pub limit: Option, @@ -177,8 +177,11 @@ impl<'a> CommentQuery<'a> { let conn = &mut get_conn(pool).await?; // 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_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.local_user.id) + .unwrap_or(LocalUserId(-1)); let mut query = comment::table .inner_join(person::table) @@ -294,12 +297,24 @@ impl<'a> CommentQuery<'a> { query = query.filter(comment_saved::comment_id.is_not_null()); } - if !self.show_deleted_and_removed.unwrap_or(true) { + let is_profile_view = self.is_profile_view.unwrap_or(false); + let is_creator = self.creator_id == self.local_user.map(|l| l.person.id); + // only show deleted comments to creator + if !is_creator { query = query.filter(comment::deleted.eq(false)); + } + + let is_admin = self.local_user.map(|l| l.person.admin).unwrap_or(false); + // only show removed comments to admin when viewing user profile + if !(is_profile_view && is_admin) { query = query.filter(comment::removed.eq(false)); } - if !self.local_user.map(|l| l.show_bot_accounts).unwrap_or(true) { + if !self + .local_user + .map(|l| l.local_user.show_bot_accounts) + .unwrap_or(true) + { query = query.filter(person::bot_account.eq(false)); }; @@ -385,17 +400,19 @@ mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] - use crate::comment_view::{ - Comment, - CommentQuery, - CommentSortType, - CommentView, - Community, - DbPool, - LocalUser, - Person, - PersonBlock, - Post, + use crate::{ + comment_view::{ + Comment, + CommentQuery, + CommentSortType, + CommentView, + Community, + DbPool, + Person, + PersonBlock, + Post, + }, + structs::LocalUserView, }; use lemmy_db_schema::{ aggregates::structs::CommentAggregates, @@ -407,7 +424,7 @@ mod tests { community::CommunityInsertForm, instance::Instance, language::Language, - local_user::LocalUserInsertForm, + local_user::{LocalUser, LocalUserInsertForm}, person::PersonInsertForm, person_block::PersonBlockForm, post::PostInsertForm, @@ -424,8 +441,7 @@ mod tests { inserted_comment_1: Comment, inserted_comment_2: Comment, inserted_post: Post, - inserted_person: Person, - inserted_local_user: LocalUser, + local_user_view: LocalUserView, inserted_person_2: Person, inserted_community: Community, } @@ -576,14 +592,18 @@ mod tests { let _inserted_comment_like = CommentLike::like(pool, &comment_like_form).await.unwrap(); + let local_user_view = LocalUserView { + local_user: inserted_local_user.clone(), + person: inserted_person.clone(), + counts: Default::default(), + }; Data { inserted_instance, inserted_comment_0, inserted_comment_1, inserted_comment_2, inserted_post, - inserted_person, - inserted_local_user, + local_user_view, inserted_person_2, inserted_community, } @@ -618,7 +638,7 @@ mod tests { let read_comment_views_with_person = CommentQuery { sort: (Some(CommentSortType::Old)), post_id: (Some(data.inserted_post.id)), - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -636,7 +656,7 @@ mod tests { let read_comment_from_blocked_person = CommentView::read( pool, data.inserted_comment_1.id, - Some(data.inserted_person.id), + Some(data.local_user_view.person.id), ) .await .unwrap(); @@ -734,7 +754,7 @@ mod tests { // by default, user has all languages enabled and should see all comments // (except from blocked user) let all_languages = CommentQuery { - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -747,11 +767,11 @@ mod tests { .await .unwrap() .unwrap(); - LocalUserLanguage::update(pool, vec![finnish_id], data.inserted_local_user.id) + LocalUserLanguage::update(pool, vec![finnish_id], data.local_user_view.local_user.id) .await .unwrap(); let finnish_comments = CommentQuery { - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -768,11 +788,15 @@ mod tests { ); // now show all comments with undetermined language (which is the default value) - LocalUserLanguage::update(pool, vec![UNDETERMINED_ID], data.inserted_local_user.id) - .await - .unwrap(); + LocalUserLanguage::update( + pool, + vec![UNDETERMINED_ID], + data.local_user_view.local_user.id, + ) + .await + .unwrap(); let undetermined_comment = CommentQuery { - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -784,9 +808,13 @@ mod tests { } async fn cleanup(data: Data, pool: &mut DbPool<'_>) { - CommentLike::remove(pool, data.inserted_person.id, data.inserted_comment_0.id) - .await - .unwrap(); + CommentLike::remove( + pool, + data.local_user_view.person.id, + data.inserted_comment_0.id, + ) + .await + .unwrap(); Comment::delete(pool, data.inserted_comment_0.id) .await .unwrap(); @@ -797,7 +825,9 @@ mod tests { Community::delete(pool, data.inserted_community.id) .await .unwrap(); - Person::delete(pool, data.inserted_person.id).await.unwrap(); + Person::delete(pool, data.local_user_view.person.id) + .await + .unwrap(); Person::delete(pool, data.inserted_person_2.id) .await .unwrap(); @@ -819,7 +849,7 @@ mod tests { comment: Comment { id: data.inserted_comment_0.id, content: "Comment 0".into(), - creator_id: data.inserted_person.id, + creator_id: data.local_user_view.person.id, post_id: data.inserted_post.id, removed: false, deleted: false, @@ -832,12 +862,12 @@ mod tests { language_id: LanguageId(37), }, creator: Person { - id: data.inserted_person.id, + id: data.local_user_view.person.id, name: "timmy".into(), display_name: None, - published: data.inserted_person.published, + published: data.local_user_view.person.published, avatar: None, - actor_id: data.inserted_person.actor_id.clone(), + actor_id: data.local_user_view.person.actor_id.clone(), local: true, banned: false, deleted: false, @@ -846,19 +876,19 @@ mod tests { bio: None, banner: None, updated: None, - inbox_url: data.inserted_person.inbox_url.clone(), + inbox_url: data.local_user_view.person.inbox_url.clone(), shared_inbox_url: None, matrix_user_id: None, ban_expires: None, instance_id: data.inserted_instance.id, - private_key: data.inserted_person.private_key.clone(), - public_key: data.inserted_person.public_key.clone(), - last_refreshed_at: data.inserted_person.last_refreshed_at, + private_key: data.local_user_view.person.private_key.clone(), + public_key: data.local_user_view.person.public_key.clone(), + last_refreshed_at: data.local_user_view.person.last_refreshed_at, }, post: Post { id: data.inserted_post.id, name: data.inserted_post.name.clone(), - creator_id: data.inserted_person.id, + creator_id: data.local_user_view.person.id, url: None, body: None, published: data.inserted_post.published, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 48faeca28..30693afb6 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -1,4 +1,4 @@ -use crate::structs::PostView; +use crate::structs::{LocalUserView, PostView}; use diesel::{ debug_query, dsl::{now, IntervalDsl}, @@ -34,7 +34,6 @@ use lemmy_db_schema::{ }, source::{ community::{Community, CommunityFollower, CommunityPersonBan}, - local_user::LocalUser, person::Person, person_block::PersonBlock, post::{Post, PostRead, PostSaved}, @@ -146,13 +145,21 @@ impl PostView { .into_boxed(); // Hide deleted and removed for non-admins or mods - // Note: one special use case for this flag variable is when end-user-delete post or mod-removed post. if !is_mod_or_admin.unwrap_or(false) { query = query .filter(community::removed.eq(false)) - .filter(community::deleted.eq(false)) .filter(post::removed.eq(false)) - .filter(post::deleted.eq(false)); + // users can see their own deleted posts + .filter( + community::deleted + .eq(false) + .or(post::creator_id.eq(person_id_join)), + ) + .filter( + post::deleted + .eq(false) + .or(post::creator_id.eq(person_id_join)), + ); } let ( @@ -199,12 +206,11 @@ pub struct PostQuery<'a> { pub sort: Option, pub creator_id: Option, pub community_id: Option, - pub local_user: Option<&'a LocalUser>, + pub local_user: Option<&'a LocalUserView>, pub search_term: Option, pub url_search: Option, pub saved_only: Option, - /// Used to show deleted or removed posts for admins - pub is_mod_or_admin: Option, + pub is_profile_view: Option, pub page: Option, pub limit: Option, } @@ -214,8 +220,11 @@ impl<'a> PostQuery<'a> { let conn = &mut get_conn(pool).await?; // 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_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.local_user.id) + .unwrap_or(LocalUserId(-1)); let mut query = post::table .inner_join(person::table) @@ -302,14 +311,21 @@ impl<'a> PostQuery<'a> { )) .into_boxed(); - // Hide deleted and removed for non-admins or mods - // TODO This eventually needs to show posts where you are the creator - if !self.is_mod_or_admin.unwrap_or(false) { + let is_profile_view = self.is_profile_view.unwrap_or(false); + let is_creator = self.creator_id == self.local_user.map(|l| l.person.id); + // only show deleted posts to creator + if is_creator { + query = query + .filter(community::deleted.eq(false)) + .filter(post::deleted.eq(false)); + } + + let is_admin = self.local_user.map(|l| l.person.admin).unwrap_or(false); + // only show removed posts to admin when viewing user profile + if !(is_profile_view && is_admin) { query = query .filter(community::removed.eq(false)) - .filter(community::deleted.eq(false)) - .filter(post::removed.eq(false)) - .filter(post::deleted.eq(false)); + .filter(post::removed.eq(false)); } if self.community_id.is_none() { @@ -359,13 +375,21 @@ impl<'a> PostQuery<'a> { ); } - if !self.local_user.map(|l| l.show_nsfw).unwrap_or(false) { + if !self + .local_user + .map(|l| l.local_user.show_nsfw) + .unwrap_or(false) + { query = query .filter(post::nsfw.eq(false)) .filter(community::nsfw.eq(false)); }; - if !self.local_user.map(|l| l.show_bot_accounts).unwrap_or(true) { + if !self + .local_user + .map(|l| l.local_user.show_bot_accounts) + .unwrap_or(true) + { query = query.filter(person::bot_account.eq(false)); }; @@ -374,7 +398,11 @@ impl<'a> PostQuery<'a> { } // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read // setting wont be able to see saved posts. - else if !self.local_user.map(|l| l.show_read_posts).unwrap_or(true) { + else if !self + .local_user + .map(|l| l.local_user.show_read_posts) + .unwrap_or(true) + { query = query.filter(post_read::post_id.is_null()); } @@ -477,7 +505,10 @@ mod tests { #![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)] - use crate::post_view::{PostQuery, PostView}; + use crate::{ + post_view::{PostQuery, PostView}, + structs::LocalUserView, + }; use lemmy_db_schema::{ aggregates::structs::PostAggregates, impls::actor_language::UNDETERMINED_ID, @@ -502,8 +533,7 @@ mod tests { struct Data { inserted_instance: Instance, - inserted_person: Person, - inserted_local_user: LocalUser, + local_user_view: LocalUserView, inserted_blocked_person: Person, inserted_bot: Person, inserted_community: Community, @@ -592,11 +622,15 @@ mod tests { .build(); let _inserted_bot_post = Post::create(pool, &new_bot_post).await.unwrap(); + let local_user_view = LocalUserView { + local_user: inserted_local_user, + person: inserted_person, + counts: Default::default(), + }; Data { inserted_instance, - inserted_person, - inserted_local_user, + local_user_view, inserted_blocked_person, inserted_bot, inserted_community, @@ -609,20 +643,21 @@ mod tests { async fn post_listing_with_person() { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let mut data = init_data(pool).await; let local_user_form = LocalUserUpdateForm::builder() .show_bot_accounts(Some(false)) .build(); let inserted_local_user = - LocalUser::update(pool, data.inserted_local_user.id, &local_user_form) + LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form) .await .unwrap(); + data.local_user_view.local_user = inserted_local_user; let read_post_listing = PostQuery { sort: (Some(SortType::New)), community_id: (Some(data.inserted_community.id)), - local_user: (Some(&inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -632,7 +667,7 @@ mod tests { let post_listing_single_with_person = PostView::read( pool, data.inserted_post.id, - Some(data.inserted_person.id), + Some(data.local_user_view.person.id), None, ) .await @@ -654,14 +689,15 @@ mod tests { .show_bot_accounts(Some(true)) .build(); let inserted_local_user = - LocalUser::update(pool, data.inserted_local_user.id, &local_user_form) + LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form) .await .unwrap(); + data.local_user_view.local_user = inserted_local_user; let post_listings_with_bots = PostQuery { sort: (Some(SortType::New)), community_id: (Some(data.inserted_community.id)), - local_user: (Some(&inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -719,7 +755,7 @@ mod tests { let data = init_data(pool).await; let community_block = CommunityBlockForm { - person_id: data.inserted_person.id, + person_id: data.local_user_view.person.id, community_id: data.inserted_community.id, }; CommunityBlock::block(pool, &community_block).await.unwrap(); @@ -727,7 +763,7 @@ mod tests { let read_post_listings_with_person_after_block = PostQuery { sort: (Some(SortType::New)), community_id: (Some(data.inserted_community.id)), - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -747,11 +783,11 @@ mod tests { async fn post_listing_like() { let pool = &build_db_pool_for_tests().await; let pool = &mut pool.into(); - let data = init_data(pool).await; + let mut data = init_data(pool).await; let post_like_form = PostLikeForm { post_id: data.inserted_post.id, - person_id: data.inserted_person.id, + person_id: data.local_user_view.person.id, score: 1, }; @@ -760,7 +796,7 @@ mod tests { let expected_post_like = PostLike { id: inserted_post_like.id, post_id: data.inserted_post.id, - person_id: data.inserted_person.id, + person_id: data.local_user_view.person.id, published: inserted_post_like.published, score: 1, }; @@ -769,7 +805,7 @@ mod tests { let post_listing_single_with_person = PostView::read( pool, data.inserted_post.id, - Some(data.inserted_person.id), + Some(data.local_user_view.person.id), None, ) .await @@ -785,14 +821,15 @@ mod tests { .show_bot_accounts(Some(false)) .build(); let inserted_local_user = - LocalUser::update(pool, data.inserted_local_user.id, &local_user_form) + LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form) .await .unwrap(); + data.local_user_view.local_user = inserted_local_user; let read_post_listing = PostQuery { sort: (Some(SortType::New)), community_id: (Some(data.inserted_community.id)), - local_user: (Some(&inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -802,9 +839,10 @@ mod tests { assert_eq!(expected_post_with_upvote, read_post_listing[0]); - let like_removed = PostLike::remove(pool, data.inserted_person.id, data.inserted_post.id) - .await - .unwrap(); + let like_removed = + PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id) + .await + .unwrap(); assert_eq!(1, like_removed); cleanup(data, pool).await; } @@ -822,7 +860,7 @@ mod tests { .unwrap(); let post_spanish = PostInsertForm::builder() .name("asffgdsc".to_string()) - .creator_id(data.inserted_person.id) + .creator_id(data.local_user_view.person.id) .community_id(data.inserted_community.id) .language_id(Some(spanish_id)) .build(); @@ -831,7 +869,7 @@ mod tests { let post_listings_all = PostQuery { sort: (Some(SortType::New)), - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -845,13 +883,13 @@ mod tests { .await .unwrap() .unwrap(); - LocalUserLanguage::update(pool, vec![french_id], data.inserted_local_user.id) + LocalUserLanguage::update(pool, vec![french_id], data.local_user_view.local_user.id) .await .unwrap(); let post_listing_french = PostQuery { sort: (Some(SortType::New)), - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -867,13 +905,13 @@ mod tests { LocalUserLanguage::update( pool, vec![french_id, UNDETERMINED_ID], - data.inserted_local_user.id, + data.local_user_view.local_user.id, ) .await .unwrap(); let post_listings_french_und = PostQuery { sort: (Some(SortType::New)), - local_user: (Some(&data.inserted_local_user)), + local_user: (Some(&data.local_user_view)), ..Default::default() } .list(pool) @@ -891,6 +929,49 @@ mod tests { cleanup(data, pool).await; } + #[tokio::test] + #[serial] + async fn post_listings_removed() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let mut data = init_data(pool).await; + + // Remove the post + Post::update( + pool, + data.inserted_post.id, + &PostUpdateForm::builder().removed(Some(true)).build(), + ) + .await + .unwrap(); + + // Make sure you don't see the removed post in the results + let post_listings_no_admin = PostQuery { + sort: Some(SortType::New), + local_user: Some(&data.local_user_view), + ..Default::default() + } + .list(pool) + .await + .unwrap(); + assert_eq!(1, post_listings_no_admin.len()); + + // Removed post is shown to admins on profile page + data.local_user_view.person.admin = true; + let post_listings_is_admin = PostQuery { + sort: Some(SortType::New), + local_user: Some(&data.local_user_view), + is_profile_view: Some(true), + ..Default::default() + } + .list(pool) + .await + .unwrap(); + assert_eq!(2, post_listings_is_admin.len()); + + cleanup(data, pool).await; + } + #[tokio::test] #[serial] async fn post_listings_deleted() { @@ -908,30 +989,33 @@ mod tests { .unwrap(); // Make sure you don't see the deleted post in the results - let post_listings_no_admin = PostQuery { - sort: (Some(SortType::New)), - local_user: (Some(&data.inserted_local_user)), - is_mod_or_admin: (Some(false)), + let post_listings_no_creator = PostQuery { + sort: Some(SortType::New), ..Default::default() } .list(pool) .await .unwrap(); + let not_contains_deleted = post_listings_no_creator + .iter() + .map(|p| p.post.id) + .all(|p| p != data.inserted_post.id); + assert!(not_contains_deleted); - assert_eq!(1, post_listings_no_admin.len()); - - // Make sure they see both - let post_listings_is_admin = PostQuery { - sort: (Some(SortType::New)), - local_user: (Some(&data.inserted_local_user)), - is_mod_or_admin: (Some(true)), + // Deleted post is shown to creator + let post_listings_is_creator = PostQuery { + sort: Some(SortType::New), + local_user: Some(&data.local_user_view), ..Default::default() } .list(pool) .await .unwrap(); - - assert_eq!(2, post_listings_is_admin.len()); + let contains_deleted = post_listings_is_creator + .iter() + .map(|p| p.post.id) + .any(|p| p == data.inserted_post.id); + assert!(contains_deleted); cleanup(data, pool).await; } @@ -941,7 +1025,9 @@ mod tests { Community::delete(pool, data.inserted_community.id) .await .unwrap(); - Person::delete(pool, data.inserted_person.id).await.unwrap(); + Person::delete(pool, data.local_user_view.person.id) + .await + .unwrap(); Person::delete(pool, data.inserted_bot.id).await.unwrap(); Person::delete(pool, data.inserted_blocked_person.id) .await @@ -954,7 +1040,7 @@ mod tests { async fn expected_post_view(data: &Data, pool: &mut DbPool<'_>) -> PostView { let (inserted_person, inserted_community, inserted_post) = ( - &data.inserted_person, + &data.local_user_view.person, &data.inserted_community, &data.inserted_post, ); diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index 49cdb27b5..3abd8eed0 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ }; use lemmy_db_views::{ post_view::PostQuery, - structs::{PostView, SiteView}, + structs::{LocalUserView, PostView, SiteView}, }; use lemmy_db_views_actor::{ comment_reply_view::CommentReplyQuery, @@ -326,7 +326,7 @@ async fn get_feed_front( ) -> Result { let site_view = SiteView::read_local(pool).await?; let local_user_id = LocalUserId(Claims::decode(jwt, jwt_secret)?.claims.sub); - let local_user = LocalUser::read(pool, local_user_id).await?; + let local_user = LocalUserView::read(pool, local_user_id).await?; let posts = PostQuery { listing_type: (Some(ListingType::Subscribed)),