From: Nutomic <me@nutomic.com>
Date: Thu, 20 Jul 2023 14:36:16 +0000 (+0200)
Subject:  Handle displaying of deleted and removed posts/comments (fixes #2624)  (#3286)
X-Git-Url: http://these/git/readmes/%7B%60https:/static/%7B%7D/inbox?a=commitdiff_plain;h=047db9ac8590670538129b5f765f47e7c66a8a80;p=lemmy.git

 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
---

diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts
index 80cb868f..d1b20d77 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 cabbcfd8..8c48dc87 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 0523712e..251f3ed9 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<LoginResponse> {
   return api.client.saveUserSettings(form);
 }
+export async function getPersonDetails(
+  api: API,
+  person_id: number,
+): Promise<GetPersonDetailsResponse> {
+  let form: GetPersonDetails = {
+    auth: api.auth,
+    person_id: person_id,
+  };
+  return api.client.getPersonDetails(form);
+}
 
 export async function deleteUser(api: API): Promise<DeleteAccountResponse> {
   let form: DeleteAccount = {
diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs
index a61da7f0..8a63f7ad 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<LemmyContext>,
-  _community_id: CommunityId,
-  person_id: PersonId,
-  post_id: PostId,
-) -> Result<PostResponse, LemmyError> {
-  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 fa1c0310..eaeb66c4 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 8feff19f..7950d504 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 b6487122..f07ce3da 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 c60abc96..2ebd6b76 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 60cea766..5d3c73c3 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 2c65f302..ca84606f 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 9a9685c7..889adeeb 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<PostId>,
   pub parent_path: Option<Ltree>,
   pub creator_id: Option<PersonId>,
-  pub local_user: Option<&'a LocalUser>,
+  pub local_user: Option<&'a LocalUserView>,
   pub search_term: Option<String>,
   pub saved_only: Option<bool>,
+  pub is_profile_view: Option<bool>,
   pub show_deleted_and_removed: Option<bool>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
@@ -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 48faeca2..30693afb 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<SortType>,
   pub creator_id: Option<PersonId>,
   pub community_id: Option<CommunityId>,
-  pub local_user: Option<&'a LocalUser>,
+  pub local_user: Option<&'a LocalUserView>,
   pub search_term: Option<String>,
   pub url_search: Option<String>,
   pub saved_only: Option<bool>,
-  /// Used to show deleted or removed posts for admins
-  pub is_mod_or_admin: Option<bool>,
+  pub is_profile_view: Option<bool>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
 }
@@ -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,16 +311,23 @@ 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::removed.eq(false))
         .filter(community::deleted.eq(false))
-        .filter(post::removed.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(post::removed.eq(false));
+    }
+
     if self.community_id.is_none() {
       query = query.then_order_by(post_aggregates::featured_local.desc());
     } else if let Some(community_id) = self.community_id {
@@ -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)
@@ -893,55 +931,103 @@ mod tests {
 
   #[tokio::test]
   #[serial]
-  async fn post_listings_deleted() {
+  async fn post_listings_removed() {
     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;
 
-    // Delete the post
+    // Remove the post
     Post::update(
       pool,
       data.inserted_post.id,
-      &PostUpdateForm::builder().deleted(Some(true)).build(),
+      &PostUpdateForm::builder().removed(Some(true)).build(),
     )
     .await
     .unwrap();
 
-    // Make sure you don't see the deleted post in the results
+    // 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.inserted_local_user)),
-      is_mod_or_admin: (Some(false)),
+      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());
 
-    // Make sure they see both
+    // 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.inserted_local_user)),
-      is_mod_or_admin: (Some(true)),
+      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() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
+    let data = init_data(pool).await;
+
+    // Delete the post
+    Post::update(
+      pool,
+      data.inserted_post.id,
+      &PostUpdateForm::builder().deleted(Some(true)).build(),
+    )
+    .await
+    .unwrap();
+
+    // Make sure you don't see the deleted post in the results
+    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);
+
+    // 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();
+    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;
+  }
+
   async fn cleanup(data: Data, pool: &mut DbPool<'_>) {
     let num_deleted = Post::delete(pool, data.inserted_post.id).await.unwrap();
     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 49cdb27b..3abd8eed 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<ChannelBuilder, LemmyError> {
   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)),