Repository: LemmyNet/lemmy (language: rust)

Issue:
Rename `when_` fields to `published`
Some of the modlog items have timestamps stored in fields called `when_`. This should be changed to `published` to be consistent with other tables. A simple search and replace should be enough.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4c3a871272..0289d35447 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2566,8 +2566,6 @@ dependencies = [
  "lemmy_api_crud",
  "lemmy_db_schema",
  "lemmy_db_views",
- "lemmy_db_views_actor",
- "lemmy_db_views_moderator",
  "lemmy_utils",
  "pretty_assertions",
  "serial_test",
@@ -2594,8 +2592,6 @@ dependencies = [
  "jsonwebtoken",
  "lemmy_db_schema",
  "lemmy_db_views",
- "lemmy_db_views_actor",
- "lemmy_db_views_moderator",
  "lemmy_utils",
  "mime",
  "mime_guess",
@@ -2632,7 +2628,6 @@ dependencies = [
  "lemmy_api_common",
  "lemmy_db_schema",
  "lemmy_db_views",
- "lemmy_db_views_actor",
  "lemmy_utils",
  "regex",
  "serde",
@@ -2662,7 +2657,6 @@ dependencies = [
  "lemmy_api_common",
  "lemmy_db_schema",
  "lemmy_db_views",
- "lemmy_db_views_actor",
  "lemmy_utils",
  "moka",
  "pretty_assertions",
@@ -2753,6 +2747,7 @@ dependencies = [
  "serde_json",
  "serde_with",
  "serial_test",
+ "strum",
  "test-context",
  "tokio",
  "tracing",
@@ -2760,43 +2755,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "lemmy_db_views_actor"
-version = "0.19.6-beta.7"
-dependencies = [
- "chrono",
- "diesel",
- "diesel-async",
- "i-love-jesus",
- "lemmy_db_schema",
- "lemmy_utils",
- "pretty_assertions",
- "serde",
- "serde_with",
- "serial_test",
- "strum",
- "tokio",
- "ts-rs",
- "url",
-]
-
-[[package]]
-name = "lemmy_db_views_moderator"
-version = "0.19.6-beta.7"
-dependencies = [
- "diesel",
- "diesel-async",
- "i-love-jesus",
- "lemmy_db_schema",
- "lemmy_utils",
- "pretty_assertions",
- "serde",
- "serde_with",
- "serial_test",
- "tokio",
- "ts-rs",
-]
-
 [[package]]
 name = "lemmy_federate"
 version = "0.19.6-beta.7"
@@ -2812,7 +2770,7 @@ dependencies = [
  "lemmy_api_common",
  "lemmy_apub",
  "lemmy_db_schema",
- "lemmy_db_views_actor",
+ "lemmy_db_views",
  "lemmy_utils",
  "mockall",
  "moka",
@@ -2847,7 +2805,6 @@ dependencies = [
  "lemmy_api_common",
  "lemmy_db_schema",
  "lemmy_db_views",
- "lemmy_db_views_actor",
  "lemmy_utils",
  "pretty_assertions",
  "prometheus",
diff --git a/Cargo.toml b/Cargo.toml
index e5cd2a74c4..9a8bac1fdc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,8 +49,6 @@ members = [
   "crates/db_perf",
   "crates/db_schema",
   "crates/db_views",
-  "crates/db_views_actor",
-  "crates/db_views_actor",
   "crates/routes",
   "crates/federate",
 ]
@@ -90,8 +88,6 @@ lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
 lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
 lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
 lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
-lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
-lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
 lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
 activitypub_federation = { version = "0.6.1", default-features = false, features = [
   "actix-web",
diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml
index 7fcb999fd7..281f8a09fe 100644
--- a/crates/api/Cargo.toml
+++ b/crates/api/Cargo.toml
@@ -21,8 +21,6 @@ workspace = true
 lemmy_utils = { workspace = true }
 lemmy_db_schema = { workspace = true, features = ["full"] }
 lemmy_db_views = { workspace = true, features = ["full"] }
-lemmy_db_views_moderator = { workspace = true, features = ["full"] }
-lemmy_db_views_actor = { workspace = true, features = ["full"] }
 lemmy_api_common = { workspace = true, features = ["full"] }
 activitypub_federation = { workspace = true }
 bcrypt = { workspace = true }
diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs
index 4c5b4eae5c..3417bc20a4 100644
--- a/crates/api/src/community/add_mod.rs
+++ b/crates/api/src/community/add_mod.rs
@@ -14,8 +14,7 @@ use lemmy_db_schema::{
   },
   traits::{Crud, Joinable},
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs
index 547838fa78..7ba0fb79e0 100644
--- a/crates/api/src/community/ban.rs
+++ b/crates/api/src/community/ban.rs
@@ -24,8 +24,7 @@ use lemmy_db_schema::{
   },
   traits::{Bannable, Crud, Followable},
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   utils::validation::is_valid_body_field,
diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs
index d498724939..e0ac782e88 100644
--- a/crates/api/src/community/block.rs
+++ b/crates/api/src/community/block.rs
@@ -12,8 +12,7 @@ use lemmy_db_schema::{
   },
   traits::{Blockable, Followable},
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityView;
+use lemmy_db_views::structs::{CommunityView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs
index d5cd3e5b19..e2ab7ee872 100644
--- a/crates/api/src/community/follow.rs
+++ b/crates/api/src/community/follow.rs
@@ -14,8 +14,7 @@ use lemmy_db_schema::{
   traits::{Crud, Followable},
   CommunityVisibility,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
+use lemmy_db_views::structs::{CommunityPersonBanView, CommunityView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/community/pending_follows/count.rs b/crates/api/src/community/pending_follows/count.rs
index e8e333c842..885ce0585b 100644
--- a/crates/api/src/community/pending_follows/count.rs
+++ b/crates/api/src/community/pending_follows/count.rs
@@ -4,8 +4,7 @@ use lemmy_api_common::{
   context::LemmyContext,
   utils::is_mod_or_admin,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::{CommunityFollowerView, LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 pub async fn get_pending_follows_count(
diff --git a/crates/api/src/community/pending_follows/list.rs b/crates/api/src/community/pending_follows/list.rs
index 9f300a74fb..bf56478f09 100644
--- a/crates/api/src/community/pending_follows/list.rs
+++ b/crates/api/src/community/pending_follows/list.rs
@@ -4,8 +4,7 @@ use lemmy_api_common::{
   context::LemmyContext,
   utils::check_community_mod_of_any_or_admin_action,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::{CommunityFollowerView, LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 pub async fn get_pending_follows_list(
diff --git a/crates/api/src/community/random.rs b/crates/api/src/community/random.rs
index 55941229d1..0d90605faf 100644
--- a/crates/api/src/community/random.rs
+++ b/crates/api/src/community/random.rs
@@ -10,8 +10,7 @@ use lemmy_db_schema::source::{
   community::Community,
   local_site::LocalSite,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityView;
+use lemmy_db_views::structs::{CommunityView, LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs
index e60b50aa20..d2e0481d09 100644
--- a/crates/api/src/community/transfer.rs
+++ b/crates/api/src/community/transfer.rs
@@ -12,8 +12,7 @@ use lemmy_db_schema::{
   },
   traits::{Crud, Joinable},
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
+use lemmy_db_views::structs::{CommunityModeratorView, CommunityView, LocalUserView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   location_info,
diff --git a/crates/api/src/local_user/add_admin.rs b/crates/api/src/local_user/add_admin.rs
index 1e821bf3ec..34d3574172 100644
--- a/crates/api/src/local_user/add_admin.rs
+++ b/crates/api/src/local_user/add_admin.rs
@@ -11,8 +11,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs
index 715bd206df..a6ec52db95 100644
--- a/crates/api/src/local_user/ban_person.rs
+++ b/crates/api/src/local_user/ban_person.rs
@@ -16,8 +16,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   utils::validation::is_valid_body_field,
diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs
index 3aee554d46..5c69835ba1 100644
--- a/crates/api/src/local_user/block.rs
+++ b/crates/api/src/local_user/block.rs
@@ -7,8 +7,7 @@ use lemmy_db_schema::{
   source::person_block::{PersonBlock, PersonBlockForm},
   traits::Blockable,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/local_user/list_banned.rs b/crates/api/src/local_user/list_banned.rs
index ba2c0d403a..d4227ca37d 100644
--- a/crates/api/src/local_user/list_banned.rs
+++ b/crates/api/src/local_user/list_banned.rs
@@ -1,7 +1,6 @@
 use actix_web::web::{Data, Json};
 use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView};
 use lemmy_utils::error::LemmyResult;
 
 pub async fn list_banned_users(
diff --git a/crates/api/src/local_user/list_saved.rs b/crates/api/src/local_user/list_saved.rs
index bdb1b6b0a9..97290c845c 100644
--- a/crates/api/src/local_user/list_saved.rs
+++ b/crates/api/src/local_user/list_saved.rs
@@ -6,7 +6,7 @@ use lemmy_api_common::{
   utils::check_private_instance,
 };
 use lemmy_db_views::{
-  person_saved_combined_view::PersonSavedCombinedQuery,
+  combined::person_saved_combined_view::PersonSavedCombinedQuery,
   structs::{LocalUserView, SiteView},
 };
 use lemmy_utils::error::LemmyResult;
diff --git a/crates/api/src/local_user/notifications/list_inbox.rs b/crates/api/src/local_user/notifications/list_inbox.rs
index 7d6e884689..2145619d7c 100644
--- a/crates/api/src/local_user/notifications/list_inbox.rs
+++ b/crates/api/src/local_user/notifications/list_inbox.rs
@@ -3,8 +3,7 @@ use lemmy_api_common::{
   context::LemmyContext,
   person::{ListInbox, ListInboxResponse},
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::inbox_combined_view::InboxCombinedQuery;
+use lemmy_db_views::{combined::inbox_combined_view::InboxCombinedQuery, structs::LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/local_user/notifications/unread_count.rs b/crates/api/src/local_user/notifications/unread_count.rs
index 4fa959329d..2a4738ea97 100644
--- a/crates/api/src/local_user/notifications/unread_count.rs
+++ b/crates/api/src/local_user/notifications/unread_count.rs
@@ -1,7 +1,6 @@
 use actix_web::web::{Data, Json};
 use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::InboxCombinedViewInternal;
+use lemmy_db_views::structs::{InboxCombinedViewInternal, LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/reports/report_combined/list.rs b/crates/api/src/reports/report_combined/list.rs
index 62df915023..646d985d22 100644
--- a/crates/api/src/reports/report_combined/list.rs
+++ b/crates/api/src/reports/report_combined/list.rs
@@ -4,7 +4,7 @@ use lemmy_api_common::{
   reports::combined::{ListReports, ListReportsResponse},
   utils::check_community_mod_of_any_or_admin_action,
 };
-use lemmy_db_views::{report_combined_view::ReportCombinedQuery, structs::LocalUserView};
+use lemmy_db_views::{combined::report_combined_view::ReportCombinedQuery, structs::LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 /// Lists reports for a community if an id is supplied
diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs
index 042009d243..0f3fae9340 100644
--- a/crates/api/src/site/leave_admin.rs
+++ b/crates/api/src/site/leave_admin.rs
@@ -12,8 +12,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::{LocalUserView, SiteView};
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView, SiteView};
 use lemmy_utils::{
   error::{LemmyErrorType, LemmyResult},
   VERSION,
diff --git a/crates/api/src/site/mod_log.rs b/crates/api/src/site/mod_log.rs
index 8c6bfdb50f..ed1c15ce2f 100644
--- a/crates/api/src/site/mod_log.rs
+++ b/crates/api/src/site/mod_log.rs
@@ -5,8 +5,7 @@ use lemmy_api_common::{
   utils::{check_community_mod_of_any_or_admin_action, check_private_instance},
 };
 use lemmy_db_schema::source::local_site::LocalSite;
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_moderator::{self, modlog_combined_view::ModlogCombinedQuery};
+use lemmy_db_views::{combined::modlog_combined_view::ModlogCombinedQuery, structs::LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs
index c55f753dce..4869831e94 100644
--- a/crates/api/src/site/purge/community.rs
+++ b/crates/api/src/site/purge/community.rs
@@ -17,8 +17,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api/src/site/registration_applications/list.rs b/crates/api/src/site/registration_applications/list.rs
index 877e83796b..cae0acd43c 100644
--- a/crates/api/src/site/registration_applications/list.rs
+++ b/crates/api/src/site/registration_applications/list.rs
@@ -7,7 +7,7 @@ use lemmy_api_common::{
 };
 use lemmy_db_schema::source::local_site::LocalSite;
 use lemmy_db_views::{
-  registration_application_view::RegistrationApplicationQuery,
+  registration_applications::registration_application_view::RegistrationApplicationQuery,
   structs::LocalUserView,
 };
 use lemmy_utils::error::LemmyResult;
diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml
index 5a2e9bf792..616b306670 100644
--- a/crates/api_common/Cargo.toml
+++ b/crates/api_common/Cargo.toml
@@ -21,8 +21,6 @@ full = [
   "tracing",
   "rosetta-i18n",
   "lemmy_db_views/full",
-  "lemmy_db_views_actor/full",
-  "lemmy_db_views_moderator/full",
   "lemmy_utils/full",
   "activitypub_federation",
   "encoding_rs",
@@ -43,8 +41,6 @@ full = [
 
 [dependencies]
 lemmy_db_views = { workspace = true }
-lemmy_db_views_moderator = { workspace = true }
-lemmy_db_views_actor = { workspace = true }
 lemmy_db_schema = { workspace = true }
 lemmy_utils = { workspace = true }
 activitypub_federation = { workspace = true, optional = true }
diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs
index 0245a04597..d7ac888a9a 100644
--- a/crates/api_common/src/build_response.rs
+++ b/crates/api_common/src/build_response.rs
@@ -25,8 +25,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
-use lemmy_db_views_actor::structs::CommunityView;
+use lemmy_db_views::structs::{CommentView, CommunityView, LocalUserView, PostView};
 use lemmy_utils::{
   error::LemmyResult,
   utils::{markdown::markdown_to_html, mention::MentionData},
diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs
index 64643e4a87..a5fadb7fdc 100644
--- a/crates/api_common/src/community.rs
+++ b/crates/api_common/src/community.rs
@@ -4,7 +4,7 @@ use lemmy_db_schema::{
   CommunityVisibility,
   ListingType,
 };
-use lemmy_db_views_actor::structs::{
+use lemmy_db_views::structs::{
   CommunityModeratorView,
   CommunitySortType,
   CommunityView,
diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs
index fde2102d5b..7eac9b21ec 100644
--- a/crates/api_common/src/lib.rs
+++ b/crates/api_common/src/lib.rs
@@ -24,8 +24,6 @@ pub mod utils;
 
 pub extern crate lemmy_db_schema;
 pub extern crate lemmy_db_views;
-pub extern crate lemmy_db_views_actor;
-pub extern crate lemmy_db_views_moderator;
 pub extern crate lemmy_utils;
 
 pub use lemmy_utils::error::LemmyErrorType;
diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs
index 67401663f8..224363700d 100644
--- a/crates/api_common/src/person.rs
+++ b/crates/api_common/src/person.rs
@@ -17,15 +17,13 @@ use lemmy_db_schema::{
   PostSortType,
 };
 use lemmy_db_views::structs::{
+  CommunityModeratorView,
+  InboxCombinedPaginationCursor,
+  InboxCombinedView,
   LocalImageView,
   PersonContentCombinedPaginationCursor,
   PersonContentCombinedView,
   PersonSavedCombinedPaginationCursor,
-};
-use lemmy_db_views_actor::structs::{
-  CommunityModeratorView,
-  InboxCombinedPaginationCursor,
-  InboxCombinedView,
   PersonView,
 };
 use serde::{Deserialize, Serialize};
diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs
index a26d6265a1..68ec332f64 100644
--- a/crates/api_common/src/post.rs
+++ b/crates/api_common/src/post.rs
@@ -4,8 +4,13 @@ use lemmy_db_schema::{
   PostFeatureType,
   PostSortType,
 };
-use lemmy_db_views::structs::{PaginationCursor, PostView, VoteView};
-use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
+use lemmy_db_views::structs::{
+  CommunityModeratorView,
+  CommunityView,
+  PaginationCursor,
+  PostView,
+  VoteView,
+};
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 #[cfg(feature = "full")]
diff --git a/crates/api_common/src/private_message.rs b/crates/api_common/src/private_message.rs
index f8134ea274..aac27499a0 100644
--- a/crates/api_common/src/private_message.rs
+++ b/crates/api_common/src/private_message.rs
@@ -1,5 +1,5 @@
 use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId};
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::PrivateMessageView;
 use serde::{Deserialize, Serialize};
 #[cfg(feature = "full")]
 use ts_rs::TS;
diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs
index 07203ffe40..b606c9a901 100644
--- a/crates/api_common/src/send_activity.rs
+++ b/crates/api_common/src/send_activity.rs
@@ -11,7 +11,7 @@ use lemmy_db_schema::{
     private_message::PrivateMessage,
   },
 };
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::PrivateMessageView;
 use lemmy_utils::error::LemmyResult;
 use std::sync::{LazyLock, OnceLock};
 use tokio::{
diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs
index 8dfb1132b9..25dcf90b3e 100644
--- a/crates/api_common/src/site.rs
+++ b/crates/api_common/src/site.rs
@@ -27,22 +27,24 @@ use lemmy_db_schema::{
   PostListingMode,
   PostSortType,
   RegistrationMode,
+  SearchSortType,
   SearchType,
 };
 use lemmy_db_views::structs::{
   CommentView,
-  LocalUserView,
-  PostView,
-  RegistrationApplicationView,
-  SiteView,
-};
-use lemmy_db_views_actor::structs::{
   CommunityFollowerView,
   CommunityModeratorView,
   CommunityView,
+  LocalUserView,
+  ModlogCombinedPaginationCursor,
+  ModlogCombinedView,
   PersonView,
+  PostView,
+  RegistrationApplicationView,
+  SearchCombinedPaginationCursor,
+  SearchCombinedView,
+  SiteView,
 };
-use lemmy_db_views_moderator::structs::{ModlogCombinedPaginationCursor, ModlogCombinedView};
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 #[cfg(feature = "full")]
@@ -52,9 +54,10 @@ use ts_rs::TS;
 #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "full", derive(TS))]
 #[cfg_attr(feature = "full", ts(export))]
-/// Searches the site, given a query string, and some optional filters.
+/// Searches the site, given a search term, and some optional filters.
 pub struct Search {
-  pub q: String,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub search_term: Option<String>,
   #[cfg_attr(feature = "full", ts(optional))]
   pub community_id: Option<CommunityId>,
   #[cfg_attr(feature = "full", ts(optional))]
@@ -64,14 +67,10 @@ pub struct Search {
   #[cfg_attr(feature = "full", ts(optional))]
   pub type_: Option<SearchType>,
   #[cfg_attr(feature = "full", ts(optional))]
-  pub sort: Option<PostSortType>,
+  pub sort: Option<SearchSortType>,
   #[cfg_attr(feature = "full", ts(optional))]
   pub listing_type: Option<ListingType>,
   #[cfg_attr(feature = "full", ts(optional))]
-  pub page: Option<i64>,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub limit: Option<i64>,
-  #[cfg_attr(feature = "full", ts(optional))]
   pub title_only: Option<bool>,
   #[cfg_attr(feature = "full", ts(optional))]
   pub post_url_only: Option<bool>,
@@ -79,19 +78,18 @@ pub struct Search {
   pub liked_only: Option<bool>,
   #[cfg_attr(feature = "full", ts(optional))]
   pub disliked_only: Option<bool>,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub page_cursor: Option<SearchCombinedPaginationCursor>,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub page_back: Option<bool>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
 #[cfg_attr(feature = "full", derive(TS))]
 #[cfg_attr(feature = "full", ts(export))]
 /// The search response, containing lists of the return type possibilities
-// TODO this should be redone as a list of tagged enums
 pub struct SearchResponse {
-  pub type_: SearchType,
-  pub comments: Vec<CommentView>,
-  pub posts: Vec<PostView>,
-  pub communities: Vec<CommunityView>,
-  pub users: Vec<PersonView>,
+  pub results: Vec<SearchCombinedView>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs
index 6a3f0f3376..fc572b0876 100644
--- a/crates/api_common/src/utils.rs
+++ b/crates/api_common/src/utils.rs
@@ -47,14 +47,16 @@ use lemmy_db_schema::{
   RegistrationMode,
 };
 use lemmy_db_views::{
-  comment_view::CommentQuery,
-  structs::{LocalImageView, LocalUserView, SiteView},
-};
-use lemmy_db_views_actor::structs::{
-  CommunityFollowerView,
-  CommunityModeratorView,
-  CommunityPersonBanView,
-  CommunityView,
+  comment::comment_view::CommentQuery,
+  structs::{
+    CommunityFollowerView,
+    CommunityModeratorView,
+    CommunityPersonBanView,
+    CommunityView,
+    LocalImageView,
+    LocalUserView,
+    SiteView,
+  },
 };
 use lemmy_utils::{
   email::{send_email, translations::Lang},
@@ -1213,8 +1215,8 @@ mod tests {
     },
     ModlogActionType,
   };
-  use lemmy_db_views_moderator::{
-    modlog_combined_view::ModlogCombinedQuery,
+  use lemmy_db_views::{
+    combined::modlog_combined_view::ModlogCombinedQuery,
     structs::{ModRemoveCommentView, ModRemovePostView, ModlogCombinedView},
   };
   use pretty_assertions::assert_eq;
diff --git a/crates/api_crud/Cargo.toml b/crates/api_crud/Cargo.toml
index 71bcfd291e..538a48216a 100644
--- a/crates/api_crud/Cargo.toml
+++ b/crates/api_crud/Cargo.toml
@@ -16,7 +16,6 @@ workspace = true
 lemmy_utils = { workspace = true, features = ["full"] }
 lemmy_db_schema = { workspace = true, features = ["full"] }
 lemmy_db_views = { workspace = true, features = ["full"] }
-lemmy_db_views_actor = { workspace = true, features = ["full"] }
 lemmy_api_common = { workspace = true, features = ["full"] }
 activitypub_federation = { workspace = true }
 bcrypt = { workspace = true }
diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs
index 7f9d049339..1482206e8b 100644
--- a/crates/api_crud/src/community/delete.rs
+++ b/crates/api_crud/src/community/delete.rs
@@ -11,8 +11,7 @@ use lemmy_db_schema::{
   source::community::{Community, CommunityUpdateForm},
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api_crud/src/community/list.rs b/crates/api_crud/src/community/list.rs
index 9c13ae89f5..6886cf3fcc 100644
--- a/crates/api_crud/src/community/list.rs
+++ b/crates/api_crud/src/community/list.rs
@@ -4,8 +4,10 @@ use lemmy_api_common::{
   context::LemmyContext,
   utils::{check_private_instance, is_admin},
 };
-use lemmy_db_views::structs::{LocalUserView, SiteView};
-use lemmy_db_views_actor::community_view::CommunityQuery;
+use lemmy_db_views::{
+  community::community_view::CommunityQuery,
+  structs::{LocalUserView, SiteView},
+};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs
index 91e47d4aa4..31f1749802 100644
--- a/crates/api_crud/src/post/create.rs
+++ b/crates/api_crud/src/post/create.rs
@@ -27,8 +27,7 @@ use lemmy_db_schema::{
   traits::{Crud, Likeable},
   utils::diesel_url_create,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   utils::{
diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs
index 3b6ef94143..10986a1199 100644
--- a/crates/api_crud/src/post/read.rs
+++ b/crates/api_crud/src/post/read.rs
@@ -12,10 +12,9 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views::{
-  post_view::PostQuery,
-  structs::{LocalUserView, PostView, SiteView},
+  post::post_view::PostQuery,
+  structs::{CommunityModeratorView, CommunityView, LocalUserView, PostView, SiteView},
 };
-use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
 use lemmy_utils::error::{LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs
index fd95a2b9ed..1a6a78d003 100644
--- a/crates/api_crud/src/private_message/create.rs
+++ b/crates/api_crud/src/private_message/create.rs
@@ -21,8 +21,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   utils::{markdown::markdown_to_html, validation::is_valid_body_field},
diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs
index d06c8bc041..30efc020ca 100644
--- a/crates/api_crud/src/private_message/delete.rs
+++ b/crates/api_crud/src/private_message/delete.rs
@@ -9,8 +9,7 @@ use lemmy_db_schema::{
   source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs
index 22c1da4a24..b9e4785efe 100644
--- a/crates/api_crud/src/private_message/update.rs
+++ b/crates/api_crud/src/private_message/update.rs
@@ -14,8 +14,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
 use lemmy_utils::{
   error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
   utils::validation::is_valid_body_field,
diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs
index 64d2237a0f..9823a08070 100644
--- a/crates/api_crud/src/site/read.rs
+++ b/crates/api_crud/src/site/read.rs
@@ -8,8 +8,7 @@ use lemmy_db_schema::source::{
   oauth_provider::OAuthProvider,
   tagline::Tagline,
 };
-use lemmy_db_views::structs::{LocalUserView, SiteView};
-use lemmy_db_views_actor::structs::PersonView;
+use lemmy_db_views::structs::{LocalUserView, PersonView, SiteView};
 use lemmy_utils::{build_cache, error::LemmyResult, CacheLock, VERSION};
 use std::sync::LazyLock;
 
diff --git a/crates/api_crud/src/user/my_user.rs b/crates/api_crud/src/user/my_user.rs
index f7a92eb997..66026d60b1 100644
--- a/crates/api_crud/src/user/my_user.rs
+++ b/crates/api_crud/src/user/my_user.rs
@@ -6,8 +6,7 @@ use lemmy_db_schema::source::{
   instance_block::InstanceBlock,
   person_block::PersonBlock,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView};
+use lemmy_db_views::structs::{CommunityFollowerView, CommunityModeratorView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml
index 75d16ff0f7..dbe6609572 100644
--- a/crates/apub/Cargo.toml
+++ b/crates/apub/Cargo.toml
@@ -21,7 +21,6 @@ workspace = true
 lemmy_utils = { workspace = true, features = ["full"] }
 lemmy_db_schema = { workspace = true, features = ["full"] }
 lemmy_db_views = { workspace = true, features = ["full"] }
-lemmy_db_views_actor = { workspace = true, features = ["full"] }
 lemmy_api_common = { workspace = true, features = ["full"] }
 activitypub_federation = { workspace = true }
 diesel = { workspace = true }
diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs
index 50ee60183d..b9208cd7e8 100644
--- a/crates/apub/src/activities/community/report.rs
+++ b/crates/apub/src/activities/community/report.rs
@@ -29,7 +29,7 @@ use lemmy_db_schema::{
   },
   traits::{Crud, Reportable},
 };
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::CommunityModeratorView;
 use lemmy_utils::error::{LemmyError, LemmyResult};
 use url::Url;
 
diff --git a/crates/apub/src/activities/create_or_update/private_message.rs b/crates/apub/src/activities/create_or_update/private_message.rs
index ce04a93301..b6e7478efa 100644
--- a/crates/apub/src/activities/create_or_update/private_message.rs
+++ b/crates/apub/src/activities/create_or_update/private_message.rs
@@ -14,7 +14,7 @@ use activitypub_federation::{
 };
 use lemmy_api_common::context::LemmyContext;
 use lemmy_db_schema::source::activity::ActivitySendTargets;
-use lemmy_db_views_actor::structs::PrivateMessageView;
+use lemmy_db_views::structs::PrivateMessageView;
 use lemmy_utils::error::{LemmyError, LemmyResult};
 use url::Url;
 
diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs
index feef9cbd03..92d1897fec 100644
--- a/crates/apub/src/activities/mod.rs
+++ b/crates/apub/src/activities/mod.rs
@@ -43,7 +43,7 @@ use lemmy_db_schema::{
   traits::Crud,
   CommunityVisibility,
 };
-use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
+use lemmy_db_views::structs::{CommunityPersonBanView, CommunityView};
 use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
 use serde::Serialize;
 use tracing::info;
diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs
index 3759d1a4bf..cd01752a0c 100644
--- a/crates/apub/src/api/list_comments.rs
+++ b/crates/apub/src/api/list_comments.rs
@@ -16,7 +16,7 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views::{
-  comment_view::CommentQuery,
+  comment::comment_view::CommentQuery,
   structs::{CommentView, LocalUserView, SiteView},
 };
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
diff --git a/crates/apub/src/api/list_person_content.rs b/crates/apub/src/api/list_person_content.rs
index 774f807664..b3f9c16b5b 100644
--- a/crates/apub/src/api/list_person_content.rs
+++ b/crates/apub/src/api/list_person_content.rs
@@ -7,7 +7,7 @@ use lemmy_api_common::{
   utils::check_private_instance,
 };
 use lemmy_db_views::{
-  person_content_combined_view::PersonContentCombinedQuery,
+  combined::person_content_combined_view::PersonContentCombinedQuery,
   structs::{LocalUserView, SiteView},
 };
 use lemmy_utils::error::LemmyResult;
diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs
index 625661b7f2..f85c3f8799 100644
--- a/crates/apub/src/api/list_posts.rs
+++ b/crates/apub/src/api/list_posts.rs
@@ -15,7 +15,7 @@ use lemmy_db_schema::{
   source::{community::Community, post::PostRead},
 };
 use lemmy_db_views::{
-  post_view::PostQuery,
+  post::post_view::PostQuery,
   structs::{LocalUserView, PaginationCursor, SiteView},
 };
 use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs
index f947691582..46195a545d 100644
--- a/crates/apub/src/api/read_community.rs
+++ b/crates/apub/src/api/read_community.rs
@@ -11,8 +11,7 @@ use lemmy_db_schema::source::{
   community::Community,
   local_site::LocalSite,
 };
-use lemmy_db_views::structs::LocalUserView;
-use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
+use lemmy_db_views::structs::{CommunityModeratorView, CommunityView, LocalUserView};
 use lemmy_utils::error::{LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs
index b79871b932..e38f6568c5 100644
--- a/crates/apub/src/api/read_person.rs
+++ b/crates/apub/src/api/read_person.rs
@@ -6,8 +6,7 @@ use lemmy_api_common::{
   person::{GetPersonDetails, GetPersonDetailsResponse},
   utils::{check_private_instance, is_admin, read_site_for_actor},
 };
-use lemmy_db_views::structs::{LocalUserView, SiteView};
-use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView};
+use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView, PersonView, SiteView};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs
index 8d2cd384f0..9b4d4551cd 100644
--- a/crates/apub/src/api/resolve_object.rs
+++ b/crates/apub/src/api/resolve_object.rs
@@ -11,8 +11,7 @@ use lemmy_api_common::{
   utils::check_private_instance,
 };
 use lemmy_db_schema::{source::local_site::LocalSite, utils::DbPool};
-use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
-use lemmy_db_views_actor::structs::{CommunityView, PersonView};
+use lemmy_db_views::structs::{CommentView, CommunityView, LocalUserView, PersonView, PostView};
 use lemmy_utils::error::{LemmyErrorExt2, LemmyErrorType, LemmyResult};
 
 #[tracing::instrument(skip(context))]
diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs
index 0ae7053d39..41e1ded39a 100644
--- a/crates/apub/src/api/search.rs
+++ b/crates/apub/src/api/search.rs
@@ -4,19 +4,13 @@ use actix_web::web::{Json, Query};
 use lemmy_api_common::{
   context::LemmyContext,
   site::{Search, SearchResponse},
-  utils::{check_conflicting_like_filters, check_private_instance, is_admin},
+  utils::{check_conflicting_like_filters, check_private_instance},
 };
-use lemmy_db_schema::{source::community::Community, utils::post_to_comment_sort_type, SearchType};
+use lemmy_db_schema::source::community::Community;
 use lemmy_db_views::{
-  comment_view::CommentQuery,
-  post_view::PostQuery,
+  combined::search_combined_view::SearchCombinedQuery,
   structs::{LocalUserView, SiteView},
 };
-use lemmy_db_views_actor::{
-  community_view::CommunityQuery,
-  person_view::PersonQuery,
-  structs::CommunitySortType,
-};
 use lemmy_utils::error::LemmyResult;
 
 #[tracing::instrument(skip(context))]
@@ -28,154 +22,43 @@ pub async fn search(
   let local_site = SiteView::read_local(&mut context.pool()).await?;
 
   check_private_instance(&local_user_view, &local_site.local_site)?;
+  check_conflicting_like_filters(data.liked_only, data.disliked_only)?;
 
-  let is_admin = local_user_view
-    .as_ref()
-    .map(|luv| is_admin(luv).is_ok())
-    .unwrap_or_default();
-
-  let mut posts = Vec::new();
-  let mut comments = Vec::new();
-  let mut communities = Vec::new();
-  let mut users = Vec::new();
-
-  // TODO no clean / non-nsfw searching rn
-
-  let Query(Search {
-    q,
-    community_id,
-    community_name,
-    creator_id,
-    type_,
-    sort,
-    listing_type,
-    page,
-    limit,
-    title_only,
-    post_url_only,
-    liked_only,
-    disliked_only,
-  }) = data;
-
-  let q = q.clone();
-  let search_type = type_.unwrap_or(SearchType::All);
-  let community_id = if let Some(name) = &community_name {
+  let community_id = if let Some(name) = &data.community_name {
     Some(
       resolve_actor_identifier::<ApubCommunity, Community>(name, &context, &local_user_view, false)
         .await?,
     )
     .map(|c| c.id)
   } else {
-    community_id
+    data.community_id
   };
-  let local_user = local_user_view.as_ref().map(|l| &l.local_user);
+  let search_term = data.search_term.clone();
 
-  check_conflicting_like_filters(liked_only, disliked_only)?;
-
-  let posts_query = PostQuery {
-    sort,
-    listing_type,
-    community_id,
-    creator_id,
-    local_user,
-    search_term: Some(q.clone()),
-    page,
-    limit,
-    title_only,
-    url_only: post_url_only,
-    liked_only,
-    disliked_only,
-    ..Default::default()
+  // parse pagination token
+  let page_after = if let Some(pa) = &data.page_cursor {
+    Some(pa.read(&mut context.pool()).await?)
+  } else {
+    None
   };
+  let page_back = data.page_back;
 
-  let comment_query = CommentQuery {
-    sort: sort.map(post_to_comment_sort_type),
-    listing_type,
-    search_term: Some(q.clone()),
+  let results = SearchCombinedQuery {
+    search_term,
     community_id,
-    creator_id,
-    local_user,
-    page,
-    limit,
-    liked_only,
-    disliked_only,
-    ..Default::default()
-  };
-
-  let community_query = CommunityQuery {
-    sort: sort.map(CommunitySortType::from),
-    listing_type,
-    search_term: Some(q.clone()),
-    title_only,
-    local_user,
-    is_mod_or_admin: is_admin,
-    page,
-    limit,
-    ..Default::default()
-  };
-
-  let person_query = PersonQuery {
-    sort,
-    search_term: Some(q.clone()),
-    listing_type,
-    page,
-    limit,
-  };
-
-  match search_type {
-    SearchType::Posts => {
-      posts = posts_query
-        .list(&local_site.site, &mut context.pool())
-        .await?;
-    }
-    SearchType::Comments => {
-      comments = comment_query
-        .list(&local_site.site, &mut context.pool())
-        .await?;
-    }
-    SearchType::Communities => {
-      communities = community_query
-        .list(&local_site.site, &mut context.pool())
-        .await?;
-    }
-    SearchType::Users => {
-      users = person_query.list(&mut context.pool()).await?;
-    }
-    SearchType::All => {
-      // If the community or creator is included, dont search communities or users
-      let community_or_creator_included =
-        community_id.is_some() || community_name.is_some() || creator_id.is_some();
-
-      posts = posts_query
-        .list(&local_site.site, &mut context.pool())
-        .await?;
-
-      comments = comment_query
-        .list(&local_site.site, &mut context.pool())
-        .await?;
-
-      communities = if community_or_creator_included {
-        vec![]
-      } else {
-        community_query
-          .list(&local_site.site, &mut context.pool())
-          .await?
-      };
-
-      users = if community_or_creator_included {
-        vec![]
-      } else {
-        person_query.list(&mut context.pool()).await?
-      };
-    }
-  };
-
-  // Return the jwt
-  Ok(Json(SearchResponse {
-    type_: search_type,
-    comments,
-    posts,
-    communities,
-    users,
-  }))
+    creator_id: data.creator_id,
+    type_: data.type_,
+    sort: data.sort,
+    listing_type: data.listing_type,
+    title_only: data.title_only,
+    post_url_only: data.post_url_only,
+    liked_only: data.liked_only,
+    disliked_only: data.disliked_only,
+    page_after,
+    page_back,
+  }
+  .list(&mut context.pool(), &local_user_view)
+  .await?;
+
+  Ok(Json(SearchResponse { results }))
 }
diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs
index d98df25adf..3180223c54 100644
--- a/crates/apub/src/api/user_settings_backup.rs
+++ b/crates/apub/src/api/user_settings_backup.rs
@@ -323,8 +323,7 @@ pub(crate) mod tests {
     },
     traits::{Crud, Followable},
   };
-  use lemmy_db_views::structs::LocalUserView;
-  use lemmy_db_views_actor::structs::CommunityFollowerView;
+  use lemmy_db_views::structs::{CommunityFollowerView, LocalUserView};
   use lemmy_utils::error::{LemmyErrorType, LemmyResult};
   use serial_test::serial;
   use std::time::Duration;
diff --git a/crates/apub/src/collections/community_follower.rs b/crates/apub/src/collections/community_follower.rs
index a4f5debbc7..7293b22973 100644
--- a/crates/apub/src/collections/community_follower.rs
+++ b/crates/apub/src/collections/community_follower.rs
@@ -10,7 +10,7 @@ use activitypub_federation::{
 };
 use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url};
 use lemmy_db_schema::aggregates::structs::CommunityAggregates;
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::CommunityFollowerView;
 use lemmy_utils::error::LemmyError;
 use url::Url;
 
diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs
index c7b925f97a..51913724c4 100644
--- a/crates/apub/src/collections/community_moderators.rs
+++ b/crates/apub/src/collections/community_moderators.rs
@@ -14,7 +14,7 @@ use lemmy_db_schema::{
   source::community::{CommunityModerator, CommunityModeratorForm},
   traits::Joinable,
 };
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::CommunityModeratorView;
 use lemmy_utils::error::{LemmyError, LemmyResult};
 use url::Url;
 
diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs
index 01199bc2bb..5e89f07f91 100644
--- a/crates/apub/src/collections/community_outbox.rs
+++ b/crates/apub/src/collections/community_outbox.rs
@@ -19,7 +19,7 @@ use activitypub_federation::{
 use futures::future::join_all;
 use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url};
 use lemmy_db_schema::{source::site::Site, utils::FETCH_LIMIT_MAX, PostSortType};
-use lemmy_db_views::post_view::PostQuery;
+use lemmy_db_views::post::post_view::PostQuery;
 use lemmy_utils::error::{LemmyError, LemmyResult};
 use url::Url;
 
diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs
index dbcc512585..f4dd23d5b3 100644
--- a/crates/apub/src/http/community.rs
+++ b/crates/apub/src/http/community.rs
@@ -23,7 +23,7 @@ use actix_web::{
 };
 use lemmy_api_common::context::LemmyContext;
 use lemmy_db_schema::{source::community::Community, traits::ApubActor, CommunityVisibility};
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::CommunityFollowerView;
 use lemmy_utils::error::{LemmyErrorType, LemmyResult};
 use serde::Deserialize;
 
diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs
index 52e0363769..dcfe16df67 100644
--- a/crates/apub/src/http/mod.rs
+++ b/crates/apub/src/http/mod.rs
@@ -18,7 +18,7 @@ use lemmy_db_schema::{
   source::{activity::SentActivity, community::Community},
   CommunityVisibility,
 };
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::CommunityFollowerView;
 use lemmy_utils::error::{FederationError, LemmyErrorExt, LemmyErrorType, LemmyResult};
 use serde::{Deserialize, Serialize};
 use std::{ops::Deref, time::Duration};
diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs
index 25ff7a91fb..a3606b8c9d 100644
--- a/crates/apub/src/objects/community.rs
+++ b/crates/apub/src/objects/community.rs
@@ -40,7 +40,7 @@ use lemmy_db_schema::{
   traits::{ApubActor, Crud},
   CommunityVisibility,
 };
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::CommunityFollowerView;
 use lemmy_utils::{
   error::{LemmyError, LemmyResult},
   spawn_try_task,
diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs
index 73d937ad56..e5ea5838ac 100644
--- a/crates/apub/src/objects/post.rs
+++ b/crates/apub/src/objects/post.rs
@@ -36,7 +36,7 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_db_views_actor::structs::CommunityModeratorView;
+use lemmy_db_views::structs::CommunityModeratorView;
 use lemmy_utils::{
   error::{LemmyError, LemmyResult},
   spawn_try_task,
diff --git a/crates/db_perf/src/main.rs b/crates/db_perf/src/main.rs
index 71554219b7..aac40d660c 100644
--- a/crates/db_perf/src/main.rs
+++ b/crates/db_perf/src/main.rs
@@ -22,7 +22,7 @@ use lemmy_db_schema::{
   utils::{build_db_pool, get_conn, now},
   PostSortType,
 };
-use lemmy_db_views::{post_view::PostQuery, structs::PaginationCursor};
+use lemmy_db_views::{post::post_view::PostQuery, structs::PaginationCursor};
 use lemmy_utils::error::{LemmyErrorExt2, LemmyResult};
 use std::num::NonZeroU32;
 use url::Url;
@@ -160,7 +160,7 @@ async fn try_main() -> LemmyResult<()> {
     .list(&site()?, &mut conn.into())
     .await?;
 
-    if let Some(post_view) = post_views.into_iter().last() {
+    if let Some(post_view) = post_views.into_iter().next_back() {
       println!("👀 getting pagination cursor data for next page");
       let cursor_data = PaginationCursor::after_post(&post_view)
         .read(&mut conn.into(), None)
diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql
index ad61f0485e..adf06c32b6 100644
--- a/crates/db_schema/replaceable_schema/triggers.sql
+++ b/crates/db_schema/replaceable_schema/triggers.sql
@@ -927,3 +927,126 @@ CREATE TRIGGER require_uplete
     FOR EACH STATEMENT
     EXECUTE FUNCTION r.require_uplete ();
 
+-- search: (post, comment, community, person)
+CREATE PROCEDURE r.create_search_combined_trigger (table_name text)
+LANGUAGE plpgsql
+AS $a$
+BEGIN
+    EXECUTE replace($b$ CREATE FUNCTION r.search_combined_thing_insert ( )
+            RETURNS TRIGGER
+            LANGUAGE plpgsql
+            AS $$
+            BEGIN
+                -- TODO need to figure out how to do the other columns here
+                INSERT INTO search_combined (published, thing_id)
+                    VALUES (NEW.published, NEW.id);
+                RETURN NEW;
+            END $$;
+    CREATE TRIGGER search_combined
+        AFTER INSERT ON thing
+        FOR EACH ROW
+        EXECUTE FUNCTION r.search_combined_thing_insert ( );
+        $b$,
+        'thing',
+        table_name);
+END;
+$a$;
+
+CALL r.create_search_combined_trigger ('post');
+
+CALL r.create_search_combined_trigger ('comment');
+
+CALL r.create_search_combined_trigger ('community');
+
+CALL r.create_search_combined_trigger ('person');
+
+-- You also need to triggers to update the `score` column.
+-- post | post_aggregates::score
+-- comment | comment_aggregates::score
+-- community | community_aggregates::users_active_monthly
+-- person | person_aggregates::post_score
+--
+-- Post score
+CREATE FUNCTION r.search_combined_post_score_update ()
+    RETURNS TRIGGER
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    UPDATE
+        search_combined
+    SET
+        score = NEW.score
+    WHERE
+        post_id = NEW.post_id;
+    RETURN NULL;
+END
+$$;
+
+CREATE TRIGGER search_combined_post_score
+    AFTER UPDATE OF score ON post_aggregates
+    FOR EACH ROW
+    EXECUTE FUNCTION r.search_combined_post_score_update ();
+
+-- Comment score
+CREATE FUNCTION r.search_combined_comment_score_update ()
+    RETURNS TRIGGER
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    UPDATE
+        search_combined
+    SET
+        score = NEW.score
+    WHERE
+        comment_id = NEW.comment_id;
+    RETURN NULL;
+END
+$$;
+
+CREATE TRIGGER search_combined_comment_score
+    AFTER UPDATE OF score ON comment_aggregates
+    FOR EACH ROW
+    EXECUTE FUNCTION r.search_combined_comment_score_update ();
+
+-- Person score
+CREATE FUNCTION r.search_combined_person_score_update ()
+    RETURNS TRIGGER
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    UPDATE
+        search_combined
+    SET
+        score = NEW.post_score
+    WHERE
+        person_id = NEW.person_id;
+    RETURN NULL;
+END
+$$;
+
+CREATE TRIGGER search_combined_person_score
+    AFTER UPDATE OF post_score ON person_aggregates
+    FOR EACH ROW
+    EXECUTE FUNCTION r.search_combined_person_score_update ();
+
+-- Community score
+CREATE FUNCTION r.search_combined_community_score_update ()
+    RETURNS TRIGGER
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    UPDATE
+        search_combined
+    SET
+        score = NEW.users_active_month
+    WHERE
+        community_id = NEW.community_id;
+    RETURN NULL;
+END
+$$;
+
+CREATE TRIGGER search_combined_community_score
+    AFTER UPDATE OF users_active_month ON community_aggregates
+    FOR EACH ROW
+    EXECUTE FUNCTION r.search_combined_community_score_update ();
+
diff --git a/crates/db_schema/src/impls/images.rs b/crates/db_schema/src/impls/images.rs
index df894f68d2..d253e998ca 100644
--- a/crates/db_schema/src/impls/images.rs
+++ b/crates/db_schema/src/impls/images.rs
@@ -66,7 +66,7 @@ impl LocalImage {
   }
 
   pub async fn delete_by_url(pool: &mut DbPool<'_>, url: &DbUrl) -> Result<Self, Error> {
-    let alias = url.as_str().split('/').last().ok_or(NotFound)?;
+    let alias = url.as_str().split('/').next_back().ok_or(NotFound)?;
     Self::delete_by_alias(pool, alias).await
   }
 }
diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs
index fd270e6d52..980e6ac8d6 100644
--- a/crates/db_schema/src/lib.rs
+++ b/crates/db_schema/src/lib.rs
@@ -101,6 +101,19 @@ pub enum CommentSortType {
   Controversial,
 }
 
+#[derive(
+  EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
+)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// The search sort types.
+pub enum SearchSortType {
+  #[default]
+  New,
+  Top,
+  Old,
+}
+
 #[derive(
   EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
 )]
@@ -166,11 +179,14 @@ pub enum PostListingMode {
   SmallCard,
 }
 
-#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(
+  EnumString, Display, Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Hash,
+)]
 #[cfg_attr(feature = "full", derive(TS))]
 #[cfg_attr(feature = "full", ts(export))]
 /// The type of content returned from a search.
 pub enum SearchType {
+  #[default]
   All,
   Comments,
   Posts,
diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs
index 66a02a79b3..91ba5b3623 100644
--- a/crates/db_schema/src/newtypes.rs
+++ b/crates/db_schema/src/newtypes.rs
@@ -220,6 +220,11 @@ pub struct ModlogCombinedId(i32);
 /// The inbox combined id
 pub struct InboxCombinedId(i32);
 
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
+#[cfg_attr(feature = "full", derive(DieselNewType))]
+/// The search combined id
+pub struct SearchCombinedId(i32);
+
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
 #[cfg_attr(feature = "full", derive(DieselNewType, TS))]
 #[cfg_attr(feature = "full", ts(export))]
diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs
index 80a1b4c6f0..84a14bf22d 100644
--- a/crates/db_schema/src/schema.rs
+++ b/crates/db_schema/src/schema.rs
@@ -995,6 +995,18 @@ diesel::table! {
     }
 }
 
+diesel::table! {
+    search_combined (id) {
+        id -> Int4,
+        published -> Timestamptz,
+        score -> Int8,
+        post_id -> Nullable<Int4>,
+        comment_id -> Nullable<Int4>,
+        community_id -> Nullable<Int4>,
+        person_id -> Nullable<Int4>,
+    }
+}
+
 diesel::table! {
     secret (id) {
         id -> Int4,
@@ -1196,6 +1208,10 @@ diesel::joinable!(report_combined -> comment_report (comment_report_id));
 diesel::joinable!(report_combined -> community_report (community_report_id));
 diesel::joinable!(report_combined -> post_report (post_report_id));
 diesel::joinable!(report_combined -> private_message_report (private_message_report_id));
+diesel::joinable!(search_combined -> comment (comment_id));
+diesel::joinable!(search_combined -> community (community_id));
+diesel::joinable!(search_combined -> person (person_id));
+diesel::joinable!(search_combined -> post (post_id));
 diesel::joinable!(site -> instance (instance_id));
 diesel::joinable!(site_aggregates -> site (site_id));
 diesel::joinable!(site_language -> language (language_id));
@@ -1274,6 +1290,7 @@ diesel::allow_tables_to_appear_in_same_query!(
     registration_application,
     remote_image,
     report_combined,
+    search_combined,
     secret,
     sent_activity,
     site,
diff --git a/crates/db_schema/src/source/combined/mod.rs b/crates/db_schema/src/source/combined/mod.rs
index 2555ef5be7..458fdd5191 100644
--- a/crates/db_schema/src/source/combined/mod.rs
+++ b/crates/db_schema/src/source/combined/mod.rs
@@ -3,3 +3,4 @@ pub mod modlog;
 pub mod person_content;
 pub mod person_saved;
 pub mod report;
+pub mod search;
diff --git a/crates/db_schema/src/source/combined/search.rs b/crates/db_schema/src/source/combined/search.rs
new file mode 100644
index 0000000000..fe7387feaf
--- /dev/null
+++ b/crates/db_schema/src/source/combined/search.rs
@@ -0,0 +1,28 @@
+use crate::newtypes::{CommentId, CommunityId, PersonId, PostId, SearchCombinedId};
+#[cfg(feature = "full")]
+use crate::schema::search_combined;
+use chrono::{DateTime, Utc};
+#[cfg(feature = "full")]
+use i_love_jesus::CursorKeysModule;
+use serde::{Deserialize, Serialize};
+use serde_with::skip_serializing_none;
+
+#[skip_serializing_none]
+#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
+#[cfg_attr(
+  feature = "full",
+  derive(Identifiable, Queryable, Selectable, CursorKeysModule)
+)]
+#[cfg_attr(feature = "full", diesel(table_name = search_combined))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", cursor_keys_module(name = search_combined_keys))]
+/// A combined table for a search (posts, comments, communities, persons)
+pub struct SearchCombined {
+  pub id: SearchCombinedId,
+  pub published: DateTime<Utc>,
+  pub score: i64,
+  pub post_id: Option<PostId>,
+  pub comment_id: Option<CommentId>,
+  pub community_id: Option<CommunityId>,
+  pub person_id: Option<PersonId>,
+}
diff --git a/crates/db_views/Cargo.toml b/crates/db_views/Cargo.toml
index f7d4b1d7af..1e1f3315db 100644
--- a/crates/db_views/Cargo.toml
+++ b/crates/db_views/Cargo.toml
@@ -42,6 +42,7 @@ actix-web = { workspace = true, optional = true }
 i-love-jesus = { workspace = true, optional = true }
 chrono = { workspace = true }
 derive-new.workspace = true
+strum = { workspace = true }
 
 [dev-dependencies]
 serial_test = { workspace = true }
diff --git a/crates/db_views_actor/src/inbox_combined_view.rs b/crates/db_views/src/combined/inbox_combined_view.rs
similarity index 98%
rename from crates/db_views_actor/src/inbox_combined_view.rs
rename to crates/db_views/src/combined/inbox_combined_view.rs
index 8dec579e2f..0e51c4ab35 100644
--- a/crates/db_views_actor/src/inbox_combined_view.rs
+++ b/crates/db_views/src/combined/inbox_combined_view.rs
@@ -40,7 +40,9 @@ use lemmy_db_schema::{
     post,
     post_actions,
     post_aggregates,
+    post_tag,
     private_message,
+    tag,
   },
   source::{
     combined::inbox::{inbox_combined_keys as key, InboxCombined},
@@ -244,6 +246,15 @@ impl InboxCombinedQuery {
 
     let community_join = post::community_id.eq(community::id);
 
+    let post_tags = post_tag::table
+      .inner_join(tag::table)
+      .select(diesel::dsl::sql::<diesel::sql_types::Json>(
+        "json_agg(tag.*)",
+      ))
+      .filter(post_tag::post_id.eq(post::id))
+      .filter(tag::deleted.eq(false))
+      .single_value();
+
     let mut query = inbox_combined::table
       .left_join(comment_reply::table)
       .left_join(person_comment_mention::table)
@@ -308,6 +319,7 @@ impl InboxCombinedQuery {
         post_actions::hidden.nullable().is_not_null(),
         post_actions::like_score.nullable(),
         image_details::all_columns.nullable(),
+        post_tags,
         private_message::all_columns.nullable(),
         // Shared
         post::all_columns.nullable(),
@@ -520,6 +532,7 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
         hidden: v.post_hidden,
         my_vote: v.my_post_vote,
         image_details: v.image_details,
+        post_tags: v.post_tags,
         banned_from_community: v.banned_from_community,
       }))
     } else if let Some(private_message) = v.private_message {
@@ -538,7 +551,7 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
 #[expect(clippy::indexing_slicing)]
 mod tests {
   use crate::{
-    inbox_combined_view::InboxCombinedQuery,
+    combined::inbox_combined_view::InboxCombinedQuery,
     structs::{InboxCombinedView, InboxCombinedViewInternal, PrivateMessageView},
   };
   use lemmy_db_schema::{
diff --git a/crates/db_views/src/combined/mod.rs b/crates/db_views/src/combined/mod.rs
new file mode 100644
index 0000000000..d6f4576ee1
--- /dev/null
+++ b/crates/db_views/src/combined/mod.rs
@@ -0,0 +1,12 @@
+#[cfg(feature = "full")]
+pub mod inbox_combined_view;
+#[cfg(feature = "full")]
+pub mod modlog_combined_view;
+#[cfg(feature = "full")]
+pub mod person_content_combined_view;
+#[cfg(feature = "full")]
+pub mod person_saved_combined_view;
+#[cfg(feature = "full")]
+pub mod report_combined_view;
+#[cfg(feature = "full")]
+pub mod search_combined_view;
diff --git a/crates/db_views_moderator/src/modlog_combined_view.rs b/crates/db_views/src/combined/modlog_combined_view.rs
similarity index 99%
rename from crates/db_views_moderator/src/modlog_combined_view.rs
rename to crates/db_views/src/combined/modlog_combined_view.rs
index cc349ec006..a492be07ce 100644
--- a/crates/db_views_moderator/src/modlog_combined_view.rs
+++ b/crates/db_views/src/combined/modlog_combined_view.rs
@@ -592,7 +592,7 @@ impl InternalToCombinedView for ModlogCombinedViewInternal {
 #[expect(clippy::indexing_slicing)]
 mod tests {
 
-  use crate::{modlog_combined_view::ModlogCombinedQuery, structs::ModlogCombinedView};
+  use crate::{combined::modlog_combined_view::ModlogCombinedQuery, structs::ModlogCombinedView};
   use lemmy_db_schema::{
     newtypes::PersonId,
     source::{
diff --git a/crates/db_views/src/person_content_combined_view.rs b/crates/db_views/src/combined/person_content_combined_view.rs
similarity index 98%
rename from crates/db_views/src/person_content_combined_view.rs
rename to crates/db_views/src/combined/person_content_combined_view.rs
index d28dc7bdfb..65b4d43e69 100644
--- a/crates/db_views/src/person_content_combined_view.rs
+++ b/crates/db_views/src/combined/person_content_combined_view.rs
@@ -3,7 +3,7 @@ use crate::structs::{
   LocalUserView,
   PersonContentCombinedPaginationCursor,
   PersonContentCombinedView,
-  PersonContentViewInternal,
+  PersonContentCombinedViewInternal,
   PostView,
 };
 use diesel::{
@@ -235,7 +235,9 @@ impl PersonContentCombinedQuery {
       // Tie breaker
       .then_desc(key::id);
 
-    let res = query.load::<PersonContentViewInternal>(conn).await?;
+    let res = query
+      .load::<PersonContentCombinedViewInternal>(conn)
+      .await?;
 
     // Map the query results to the enum
     let out = res
@@ -247,7 +249,7 @@ impl PersonContentCombinedQuery {
   }
 }
 
-impl InternalToCombinedView for PersonContentViewInternal {
+impl InternalToCombinedView for PersonContentCombinedViewInternal {
   type CombinedView = PersonContentCombinedView;
 
   fn map_to_enum(self) -> Option<Self::CombinedView> {
@@ -299,7 +301,7 @@ impl InternalToCombinedView for PersonContentViewInternal {
 mod tests {
 
   use crate::{
-    person_content_combined_view::PersonContentCombinedQuery,
+    combined::person_content_combined_view::PersonContentCombinedQuery,
     structs::PersonContentCombinedView,
   };
   use lemmy_db_schema::{
diff --git a/crates/db_views/src/person_saved_combined_view.rs b/crates/db_views/src/combined/person_saved_combined_view.rs
similarity index 98%
rename from crates/db_views/src/person_saved_combined_view.rs
rename to crates/db_views/src/combined/person_saved_combined_view.rs
index 5900942790..4ac7ee3fe7 100644
--- a/crates/db_views/src/person_saved_combined_view.rs
+++ b/crates/db_views/src/combined/person_saved_combined_view.rs
@@ -1,7 +1,7 @@
 use crate::structs::{
   LocalUserView,
   PersonContentCombinedView,
-  PersonContentViewInternal,
+  PersonContentCombinedViewInternal,
   PersonSavedCombinedPaginationCursor,
 };
 use diesel::{
@@ -236,7 +236,9 @@ impl PersonSavedCombinedQuery {
       // Tie breaker
       .then_desc(key::id);
 
-    let res = query.load::<PersonContentViewInternal>(conn).await?;
+    let res = query
+      .load::<PersonContentCombinedViewInternal>(conn)
+      .await?;
 
     // Map the query results to the enum
     let out = res
@@ -253,7 +255,7 @@ impl PersonSavedCombinedQuery {
 mod tests {
 
   use crate::{
-    person_saved_combined_view::PersonSavedCombinedQuery,
+    combined::person_saved_combined_view::PersonSavedCombinedQuery,
     structs::{LocalUserView, PersonContentCombinedView},
   };
   use lemmy_db_schema::{
diff --git a/crates/db_views/src/report_combined_view.rs b/crates/db_views/src/combined/report_combined_view.rs
similarity index 99%
rename from crates/db_views/src/report_combined_view.rs
rename to crates/db_views/src/combined/report_combined_view.rs
index 201e02728b..0fce1afcb5 100644
--- a/crates/db_views/src/report_combined_view.rs
+++ b/crates/db_views/src/combined/report_combined_view.rs
@@ -491,7 +491,7 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
 mod tests {
 
   use crate::{
-    report_combined_view::ReportCombinedQuery,
+    combined::report_combined_view::ReportCombinedQuery,
     structs::{
       CommentReportView,
       LocalUserView,
diff --git a/crates/db_views/src/combined/search_combined_view.rs b/crates/db_views/src/combined/search_combined_view.rs
new file mode 100644
index 0000000000..13054da0a8
--- /dev/null
+++ b/crates/db_views/src/combined/search_combined_view.rs
@@ -0,0 +1,1194 @@
+use crate::structs::{
+  CommentView,
+  CommunityView,
+  LocalUserView,
+  PersonView,
+  PostView,
+  SearchCombinedPaginationCursor,
+  SearchCombinedView,
+  SearchCombinedViewInternal,
+};
+use diesel::{
+  dsl::not,
+  result::Error,
+  BoolExpressionMethods,
+  ExpressionMethods,
+  JoinOnDsl,
+  NullableExpressionMethods,
+  PgTextExpressionMethods,
+  QueryDsl,
+  SelectableHelper,
+};
+use diesel_async::RunQueryDsl;
+use i_love_jesus::PaginatedQueryBuilder;
+use lemmy_db_schema::{
+  aliases::creator_community_actions,
+  newtypes::{CommunityId, PersonId},
+  schema::{
+    comment,
+    comment_actions,
+    comment_aggregates,
+    community,
+    community_actions,
+    community_aggregates,
+    image_details,
+    local_user,
+    person,
+    person_actions,
+    person_aggregates,
+    post,
+    post_actions,
+    post_aggregates,
+    post_tag,
+    search_combined,
+    tag,
+  },
+  source::{
+    combined::search::{search_combined_keys as key, SearchCombined},
+    community::CommunityFollower,
+  },
+  traits::InternalToCombinedView,
+  utils::{
+    actions,
+    actions_alias,
+    functions::coalesce,
+    fuzzy_search,
+    get_conn,
+    DbPool,
+    ReverseTimestampKey,
+  },
+  ListingType,
+  SearchSortType,
+  SearchType,
+};
+use lemmy_utils::error::LemmyResult;
+use SearchSortType::*;
+
+impl SearchCombinedPaginationCursor {
+  // get cursor for page that starts immediately after the given post
+  pub fn after_post(view: &SearchCombinedView) -> SearchCombinedPaginationCursor {
+    let (prefix, id) = match view {
+      SearchCombinedView::Post(v) => ('P', v.post.id.0),
+      SearchCombinedView::Comment(v) => ('C', v.comment.id.0),
+      SearchCombinedView::Community(v) => ('O', v.community.id.0),
+      SearchCombinedView::Person(v) => ('E', v.person.id.0),
+    };
+    // hex encoding to prevent ossification
+    SearchCombinedPaginationCursor(format!("{prefix}{id:x}"))
+  }
+
+  pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
+    let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
+    let mut query = search_combined::table
+      .select(SearchCombined::as_select())
+      .into_boxed();
+    let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
+    let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
+    query = match prefix {
+      "P" => query.filter(search_combined::post_id.eq(id)),
+      "C" => query.filter(search_combined::comment_id.eq(id)),
+      "O" => query.filter(search_combined::community_id.eq(id)),
+      "E" => query.filter(search_combined::person_id.eq(id)),
+      _ => return Err(err_msg()),
+    };
+    let token = query.first(&mut get_conn(pool).await?).await?;
+
+    Ok(PaginationCursorData(token))
+  }
+}
+
+#[derive(Clone)]
+pub struct PaginationCursorData(SearchCombined);
+
+#[derive(Default)]
+pub struct SearchCombinedQuery {
+  pub search_term: Option<String>,
+  pub community_id: Option<CommunityId>,
+  pub creator_id: Option<PersonId>,
+  pub type_: Option<SearchType>,
+  pub sort: Option<SearchSortType>,
+  pub listing_type: Option<ListingType>,
+  pub title_only: Option<bool>,
+  pub post_url_only: Option<bool>,
+  pub liked_only: Option<bool>,
+  pub disliked_only: Option<bool>,
+  pub page_after: Option<PaginationCursorData>,
+  pub page_back: Option<bool>,
+}
+
+impl SearchCombinedQuery {
+  pub async fn list(
+    self,
+    pool: &mut DbPool<'_>,
+    user: &Option<LocalUserView>,
+  ) -> LemmyResult<Vec<SearchCombinedView>> {
+    let my_person_id = user.as_ref().map(|u| u.local_user.person_id);
+    let item_creator = person::id;
+
+    let conn = &mut get_conn(pool).await?;
+
+    let post_tags = post_tag::table
+      .inner_join(tag::table)
+      .select(diesel::dsl::sql::<diesel::sql_types::Json>(
+        "json_agg(tag.*)",
+      ))
+      .filter(post_tag::post_id.eq(post::id))
+      .filter(tag::deleted.eq(false))
+      .single_value();
+
+    let item_creator_join = search_combined::person_id
+      .eq(item_creator.nullable())
+      .or(
+        search_combined::comment_id
+          .is_not_null()
+          .and(comment::creator_id.eq(item_creator)),
+      )
+      .or(
+        search_combined::post_id
+          .is_not_null()
+          .and(post::creator_id.eq(item_creator)),
+      )
+      .and(not(person::deleted));
+
+    let comment_join = search_combined::comment_id
+      .eq(comment::id.nullable())
+      .and(not(comment::removed))
+      .and(not(comment::deleted));
+
+    let post_join = search_combined::post_id
+      .eq(post::id.nullable())
+      .or(comment::post_id.eq(post::id))
+      .and(not(post::removed))
+      .and(not(post::deleted));
+
+    let community_join = search_combined::community_id
+      .eq(community::id.nullable())
+      .or(post::community_id.eq(community::id))
+      .and(not(community::removed))
+      .and(not(community::deleted));
+
+    // Notes: since the post_id and comment_id are optional columns,
+    // many joins must use an OR condition.
+    // For example, the creator must be the person table joined to either:
+    // - post.creator_id
+    // - comment.creator_id
+    let mut query = search_combined::table
+      // The comment
+      .left_join(comment::table.on(comment_join))
+      // The post
+      .left_join(post::table.on(post_join))
+      // The item creator
+      .left_join(person::table.on(item_creator_join))
+      // The community
+      .left_join(community::table.on(community_join))
+      .left_join(actions_alias(
+        creator_community_actions,
+        item_creator,
+        community::id,
+      ))
+      .left_join(
+        local_user::table.on(
+          item_creator
+            .eq(local_user::person_id)
+            .and(local_user::admin.eq(true)),
+        ),
+      )
+      .left_join(actions(
+        community_actions::table,
+        my_person_id,
+        community::id,
+      ))
+      .left_join(actions(post_actions::table, my_person_id, post::id))
+      .left_join(actions(person_actions::table, my_person_id, item_creator))
+      .left_join(
+        person_aggregates::table
+          .on(search_combined::person_id.eq(person_aggregates::person_id.nullable())),
+      )
+      .left_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
+      .left_join(
+        comment_aggregates::table
+          .on(search_combined::comment_id.eq(comment_aggregates::comment_id.nullable())),
+      )
+      .left_join(
+        community_aggregates::table
+          .on(search_combined::community_id.eq(community_aggregates::community_id.nullable())),
+      )
+      .left_join(actions(comment_actions::table, my_person_id, comment::id))
+      .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
+      .select((
+        // Post-specific
+        post::all_columns.nullable(),
+        post_aggregates::all_columns.nullable(),
+        coalesce(
+          post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
+          post_aggregates::comments,
+        )
+        .nullable(),
+        post_actions::saved.nullable().is_not_null(),
+        post_actions::read.nullable().is_not_null(),
+        post_actions::hidden.nullable().is_not_null(),
+        post_actions::like_score.nullable(),
+        image_details::all_columns.nullable(),
+        post_tags,
+        // Comment-specific
+        comment::all_columns.nullable(),
+        comment_aggregates::all_columns.nullable(),
+        comment_actions::saved.nullable().is_not_null(),
+        comment_actions::like_score.nullable(),
+        // Community-specific
+        community::all_columns.nullable(),
+        community_aggregates::all_columns.nullable(),
+        community_actions::blocked.nullable().is_not_null(),
+        CommunityFollower::select_subscribed_type(),
+        // Person
+        person_aggregates::all_columns.nullable(),
+        // // Shared
+        person::all_columns.nullable(),
+        local_user::admin.nullable().is_not_null(),
+        creator_community_actions
+          .field(community_actions::became_moderator)
+          .nullable()
+          .is_not_null(),
+        creator_community_actions
+          .field(community_actions::received_ban)
+          .nullable()
+          .is_not_null(),
+        person_actions::blocked.nullable().is_not_null(),
+        community_actions::received_ban.nullable().is_not_null(),
+      ))
+      .into_boxed();
+
+    // The filters
+
+    // The search term
+    if let Some(search_term) = &self.search_term {
+      if self.post_url_only.unwrap_or_default() {
+        query = query.filter(post::url.eq(search_term));
+      } else {
+        let searcher = fuzzy_search(search_term);
+
+        let name_or_title_filter = post::name
+          .ilike(searcher.clone())
+          .or(comment::content.ilike(searcher.clone()))
+          .or(community::name.ilike(searcher.clone()))
+          .or(community::title.ilike(searcher.clone()))
+          .or(person::name.ilike(searcher.clone()))
+          .or(person::display_name.ilike(searcher.clone()));
+
+        let body_or_description_filter = post::body
+          .ilike(searcher.clone())
+          .or(community::description.ilike(searcher.clone()));
+
+        query = if self.title_only.unwrap_or_default() {
+          query.filter(name_or_title_filter)
+        } else {
+          query.filter(name_or_title_filter.or(body_or_description_filter))
+        }
+      }
+    }
+
+    // Community id
+    if let Some(community_id) = self.community_id {
+      query = query.filter(community::id.eq(community_id));
+    }
+
+    // Creator id
+    if let Some(creator_id) = self.creator_id {
+      query = query.filter(item_creator.eq(creator_id));
+    }
+
+    // Liked / disliked filter
+    if let Some(my_id) = my_person_id {
+      let not_creator_filter = item_creator.ne(my_id);
+      let liked_disliked_filter = |score: i16| {
+        search_combined::post_id
+          .is_not_null()
+          .and(post_actions::like_score.eq(score))
+          .or(
+            search_combined::comment_id
+              .is_not_null()
+              .and(comment_actions::like_score.eq(score)),
+          )
+      };
+
+      if self.liked_only.unwrap_or_default() {
+        query = query
+          .filter(not_creator_filter)
+          .filter(liked_disliked_filter(1));
+      } else if self.disliked_only.unwrap_or_default() {
+        query = query
+          .filter(not_creator_filter)
+          .filter(liked_disliked_filter(-1));
+      }
+    };
+
+    // Type
+    query = match self.type_.unwrap_or_default() {
+      SearchType::All => query,
+      SearchType::Posts => query.filter(search_combined::post_id.is_not_null()),
+      SearchType::Comments => query.filter(search_combined::comment_id.is_not_null()),
+      SearchType::Communities => query.filter(search_combined::community_id.is_not_null()),
+      SearchType::Users => query.filter(search_combined::person_id.is_not_null()),
+    };
+
+    // Listing type
+    let is_subscribed = community_actions::followed.is_not_null();
+    match self.listing_type.unwrap_or_default() {
+      ListingType::Subscribed => query = query.filter(is_subscribed),
+      ListingType::Local => {
+        query = query.filter(
+          community::local
+            .eq(true)
+            .and(community::hidden.eq(false).or(is_subscribed))
+            .or(search_combined::person_id.is_not_null().and(person::local)),
+        );
+      }
+      ListingType::All => {
+        query = query.filter(
+          community::hidden
+            .eq(false)
+            .or(is_subscribed)
+            .or(search_combined::person_id.is_not_null()),
+        )
+      }
+      ListingType::ModeratorView => {
+        query = query.filter(community_actions::became_moderator.is_not_null());
+      }
+    }
+
+    let mut query = PaginatedQueryBuilder::new(query);
+
+    let page_after = self.page_after.map(|c| c.0);
+
+    if self.page_back.unwrap_or_default() {
+      query = query.before(page_after).limit_and_offset_from_end();
+    } else {
+      query = query.after(page_after);
+    }
+
+    query = match self.sort.unwrap_or_default() {
+      New => query.then_desc(key::published),
+      Old => query.then_desc(ReverseTimestampKey(key::published)),
+      Top => query.then_desc(key::score),
+    };
+    // finally use unique id as tie breaker
+    query = query.then_desc(key::id);
+
+    let res = query.load::<SearchCombinedViewInternal>(conn).await?;
+
+    // Map the query results to the enum
+    let out = res
+      .into_iter()
+      .filter_map(InternalToCombinedView::map_to_enum)
+      .collect();
+
+    Ok(out)
+  }
+}
+
+impl InternalToCombinedView for SearchCombinedViewInternal {
+  type CombinedView = SearchCombinedView;
+
+  fn map_to_enum(self) -> Option<Self::CombinedView> {
+    // Use for a short alias
+    let v = self;
+
+    if let (Some(comment), Some(counts), Some(creator), Some(post), Some(community)) = (
+      v.comment,
+      v.comment_counts,
+      v.item_creator.clone(),
+      v.post.clone(),
+      v.community.clone(),
+    ) {
+      Some(SearchCombinedView::Comment(CommentView {
+        comment,
+        counts,
+        post,
+        community,
+        creator,
+        creator_banned_from_community: v.item_creator_banned_from_community,
+        creator_is_moderator: v.item_creator_is_moderator,
+        creator_is_admin: v.item_creator_is_admin,
+        creator_blocked: v.item_creator_blocked,
+        subscribed: v.subscribed,
+        saved: v.comment_saved,
+        my_vote: v.my_comment_vote,
+        banned_from_community: v.banned_from_community,
+      }))
+    } else if let (
+      Some(post),
+      Some(counts),
+      Some(creator),
+      Some(community),
+      Some(unread_comments),
+    ) = (
+      v.post,
+      v.post_counts,
+      v.item_creator.clone(),
+      v.community.clone(),
+      v.post_unread_comments,
+    ) {
+      Some(SearchCombinedView::Post(PostView {
+        post,
+        community,
+        counts,
+        unread_comments,
+        creator,
+        creator_banned_from_community: v.item_creator_banned_from_community,
+        creator_is_moderator: v.item_creator_is_moderator,
+        creator_is_admin: v.item_creator_is_admin,
+        creator_blocked: v.item_creator_blocked,
+        subscribed: v.subscribed,
+        saved: v.post_saved,
+        read: v.post_read,
+        hidden: v.post_hidden,
+        my_vote: v.my_post_vote,
+        image_details: v.image_details,
+        banned_from_community: v.banned_from_community,
+        tags: v.post_tags,
+      }))
+    } else if let (Some(community), Some(counts)) = (v.community, v.community_counts) {
+      Some(SearchCombinedView::Community(CommunityView {
+        community,
+        counts,
+        subscribed: v.subscribed,
+        blocked: v.community_blocked,
+        banned_from_community: v.banned_from_community,
+      }))
+    } else if let (Some(person), Some(counts)) = (v.item_creator, v.item_creator_counts) {
+      Some(SearchCombinedView::Person(PersonView {
+        person,
+        counts,
+        is_admin: v.item_creator_is_admin,
+      }))
+    } else {
+      None
+    }
+  }
+}
+
+#[cfg(test)]
+#[expect(clippy::indexing_slicing)]
+mod tests {
+
+  use crate::{
+    combined::search_combined_view::SearchCombinedQuery,
+    structs::{LocalUserView, SearchCombinedView},
+  };
+  use lemmy_db_schema::{
+    assert_length,
+    source::{
+      comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
+      community::{Community, CommunityInsertForm},
+      instance::Instance,
+      local_user::{LocalUser, LocalUserInsertForm},
+      local_user_vote_display_mode::LocalUserVoteDisplayMode,
+      person::{Person, PersonInsertForm},
+      post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
+    },
+    traits::{Crud, Likeable},
+    utils::{build_db_pool_for_tests, DbPool},
+    SearchSortType,
+    SearchType,
+  };
+  use lemmy_utils::error::LemmyResult;
+  use pretty_assertions::assert_eq;
+  use serial_test::serial;
+  use url::Url;
+
+  struct Data {
+    instance: Instance,
+    timmy: Person,
+    timmy_view: LocalUserView,
+    sara: Person,
+    community: Community,
+    community_2: Community,
+    timmy_post: Post,
+    timmy_post_2: Post,
+    sara_post: Post,
+    timmy_comment: Comment,
+    sara_comment: Comment,
+    sara_comment_2: Comment,
+  }
+
+  async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
+    let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
+
+    let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv");
+    let sara = Person::create(pool, &sara_form).await?;
+
+    let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv");
+    let timmy = Person::create(pool, &timmy_form).await?;
+    let timmy_local_user_form = LocalUserInsertForm::test_form(timmy.id);
+    let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
+    let timmy_view = LocalUserView {
+      local_user: timmy_local_user,
+      local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
+      person: timmy.clone(),
+      counts: Default::default(),
+    };
+
+    let community_form = CommunityInsertForm {
+      description: Some("ask lemmy things".into()),
+      ..CommunityInsertForm::new(
+        instance.id,
+        "asklemmy".to_string(),
+        "Ask Lemmy".to_owned(),
+        "pubkey".to_string(),
+      )
+    };
+    let community = Community::create(pool, &community_form).await?;
+
+    let community_form_2 = CommunityInsertForm::new(
+      instance.id,
+      "startrek_ds9".to_string(),
+      "Star Trek - Deep Space Nine".to_owned(),
+      "pubkey".to_string(),
+    );
+    let community_2 = Community::create(pool, &community_form_2).await?;
+
+    let timmy_post_form = PostInsertForm {
+      body: Some("postbody inside here".into()),
+      url: Some(Url::parse("https://google.com")?.into()),
+      ..PostInsertForm::new("timmy post prv".into(), timmy.id, community.id)
+    };
+    let timmy_post = Post::create(pool, &timmy_post_form).await?;
+
+    let timmy_post_form_2 = PostInsertForm::new("timmy post prv 2".into(), timmy.id, community.id);
+    let timmy_post_2 = Post::create(pool, &timmy_post_form_2).await?;
+
+    let sara_post_form = PostInsertForm::new("sara post prv".into(), sara.id, community_2.id);
+    let sara_post = Post::create(pool, &sara_post_form).await?;
+
+    let timmy_comment_form =
+      CommentInsertForm::new(timmy.id, timmy_post.id, "timmy comment prv gold".into());
+    let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?;
+
+    let sara_comment_form =
+      CommentInsertForm::new(sara.id, sara_post.id, "sara comment prv gold".into());
+    let sara_comment = Comment::create(pool, &sara_comment_form, None).await?;
+
+    let sara_comment_form_2 =
+      CommentInsertForm::new(sara.id, timmy_post_2.id, "sara comment prv 2".into());
+    let sara_comment_2 = Comment::create(pool, &sara_comment_form_2, None).await?;
+
+    // Timmy likes and dislikes a few things
+    let timmy_like_post_form = PostLikeForm::new(timmy_post.id, timmy.id, 1);
+    PostLike::like(pool, &timmy_like_post_form).await?;
+
+    let timmy_like_sara_post_form = PostLikeForm::new(sara_post.id, timmy.id, 1);
+    PostLike::like(pool, &timmy_like_sara_post_form).await?;
+
+    let timmy_dislike_post_form = PostLikeForm::new(timmy_post_2.id, timmy.id, -1);
+    PostLike::like(pool, &timmy_dislike_post_form).await?;
+
+    let timmy_like_comment_form = CommentLikeForm {
+      person_id: timmy.id,
+      comment_id: timmy_comment.id,
+      score: 1,
+    };
+    CommentLike::like(pool, &timmy_like_comment_form).await?;
+
+    let timmy_like_sara_comment_form = CommentLikeForm {
+      person_id: timmy.id,
+      comment_id: sara_comment.id,
+      score: 1,
+    };
+    CommentLike::like(pool, &timmy_like_sara_comment_form).await?;
+
+    let timmy_dislike_sara_comment_form = CommentLikeForm {
+      person_id: timmy.id,
+      comment_id: sara_comment_2.id,
+      score: -1,
+    };
+    CommentLike::like(pool, &timmy_dislike_sara_comment_form).await?;
+
+    Ok(Data {
+      instance,
+      timmy,
+      timmy_view,
+      sara,
+      community,
+      community_2,
+      timmy_post,
+      timmy_post_2,
+      sara_post,
+      timmy_comment,
+      sara_comment,
+      sara_comment_2,
+    })
+  }
+
+  async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
+    Instance::delete(pool, data.instance.id).await?;
+
+    Ok(())
+  }
+
+  #[tokio::test]
+  #[serial]
+  async fn combined() -> LemmyResult<()> {
+    let pool = &build_db_pool_for_tests();
+    let pool = &mut pool.into();
+    let data = init_data(pool).await?;
+
+    // search
+    let search = SearchCombinedQuery::default().list(pool, &None).await?;
+    assert_length!(10, search);
+
+    // Make sure the types are correct
+    if let SearchCombinedView::Comment(v) = &search[0] {
+      assert_eq!(data.sara_comment_2.id, v.comment.id);
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Comment(v) = &search[1] {
+      assert_eq!(data.sara_comment.id, v.comment.id);
+      assert_eq!(data.sara_post.id, v.post.id);
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Comment(v) = &search[2] {
+      assert_eq!(data.timmy_comment.id, v.comment.id);
+      assert_eq!(data.timmy_post.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Post(v) = &search[3] {
+      assert_eq!(data.sara_post.id, v.post.id);
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Post(v) = &search[4] {
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Post(v) = &search[5] {
+      assert_eq!(data.timmy_post.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Community(v) = &search[6] {
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Community(v) = &search[7] {
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Person(v) = &search[8] {
+      assert_eq!(data.timmy.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Person(v) = &search[9] {
+      assert_eq!(data.sara.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Filtered by community id
+    let search_by_community = SearchCombinedQuery {
+      community_id: Some(data.community.id),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(5, search_by_community);
+
+    // Filtered by creator_id
+    let search_by_creator = SearchCombinedQuery {
+      creator_id: Some(data.timmy.id),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(4, search_by_creator);
+
+    // Using a term
+    let search_by_name = SearchCombinedQuery {
+      search_term: Some("gold".into()),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(2, search_by_name);
+
+    // Liked / disliked only
+    let search_liked_only = SearchCombinedQuery {
+      liked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    assert_length!(2, search_liked_only);
+
+    let search_disliked_only = SearchCombinedQuery {
+      disliked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    assert_length!(1, search_disliked_only);
+
+    // Test sorts
+    // Test Old sort
+    let search_old_sort = SearchCombinedQuery {
+      sort: Some(SearchSortType::Old),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+    if let SearchCombinedView::Person(v) = &search_old_sort[0] {
+      assert_eq!(data.sara.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+    assert_length!(10, search_old_sort);
+
+    // Remove a post and delete a comment
+    Post::update(
+      pool,
+      data.timmy_post_2.id,
+      &PostUpdateForm {
+        removed: Some(true),
+        ..Default::default()
+      },
+    )
+    .await?;
+
+    Comment::update(
+      pool,
+      data.sara_comment.id,
+      &CommentUpdateForm {
+        deleted: Some(true),
+        ..Default::default()
+      },
+    )
+    .await?;
+
+    // 2 things got removed, but the post also has another comment which got removed
+    let search = SearchCombinedQuery::default().list(pool, &None).await?;
+    assert_length!(7, search);
+
+    cleanup(data, pool).await?;
+
+    Ok(())
+  }
+
+  #[tokio::test]
+  #[serial]
+  async fn community() -> LemmyResult<()> {
+    let pool = &build_db_pool_for_tests();
+    let pool = &mut pool.into();
+    let data = init_data(pool).await?;
+
+    // Community search
+    let community_search = SearchCombinedQuery {
+      type_: Some(SearchType::Communities),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(2, community_search);
+
+    // Make sure the types are correct
+    if let SearchCombinedView::Community(v) = &community_search[0] {
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Community(v) = &community_search[1] {
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Filtered by id
+    let community_search_by_id = SearchCombinedQuery {
+      community_id: Some(data.community.id),
+      type_: Some(SearchType::Communities),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(1, community_search_by_id);
+
+    // Using a term
+    let community_search_by_name = SearchCombinedQuery {
+      search_term: Some("things".into()),
+      type_: Some(SearchType::Communities),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(1, community_search_by_name);
+    if let SearchCombinedView::Community(v) = &community_search_by_name[0] {
+      // The asklemmy community
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Test title only search to make sure 'ask lemmy things' doesn't get returned
+    // Using a term
+    let community_search_title_only = SearchCombinedQuery {
+      search_term: Some("things".into()),
+      type_: Some(SearchType::Communities),
+      title_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert!(community_search_title_only.is_empty());
+
+    cleanup(data, pool).await?;
+
+    Ok(())
+  }
+
+  #[tokio::test]
+  #[serial]
+  async fn person() -> LemmyResult<()> {
+    let pool = &build_db_pool_for_tests();
+    let pool = &mut pool.into();
+    let data = init_data(pool).await?;
+
+    // Person search
+    let person_search = SearchCombinedQuery {
+      type_: Some(SearchType::Users),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(2, person_search);
+
+    // Make sure the types are correct
+    if let SearchCombinedView::Person(v) = &person_search[0] {
+      assert_eq!(data.timmy.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Person(v) = &person_search[1] {
+      assert_eq!(data.sara.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Filtered by creator_id
+    let person_search_by_id = SearchCombinedQuery {
+      creator_id: Some(data.sara.id),
+      type_: Some(SearchType::Users),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(1, person_search_by_id);
+    if let SearchCombinedView::Person(v) = &person_search_by_id[0] {
+      assert_eq!(data.sara.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Using a term
+    let person_search_by_name = SearchCombinedQuery {
+      search_term: Some("tim".into()),
+      type_: Some(SearchType::Users),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(1, person_search_by_name);
+    if let SearchCombinedView::Person(v) = &person_search_by_name[0] {
+      assert_eq!(data.timmy.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Test Top sorting (uses post score)
+    let person_search_sort_top = SearchCombinedQuery {
+      type_: Some(SearchType::Users),
+      sort: Some(SearchSortType::Top),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(2, person_search_sort_top);
+
+    // Sara should be first, as she has a higher score
+    if let SearchCombinedView::Person(v) = &person_search_sort_top[0] {
+      assert_eq!(data.sara.id, v.person.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    cleanup(data, pool).await?;
+
+    Ok(())
+  }
+
+  #[tokio::test]
+  #[serial]
+  async fn post() -> LemmyResult<()> {
+    let pool = &build_db_pool_for_tests();
+    let pool = &mut pool.into();
+    let data = init_data(pool).await?;
+
+    // post search
+    let post_search = SearchCombinedQuery {
+      type_: Some(SearchType::Posts),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(3, post_search);
+
+    // Make sure the types are correct
+    if let SearchCombinedView::Post(v) = &post_search[0] {
+      assert_eq!(data.sara_post.id, v.post.id);
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Post(v) = &post_search[1] {
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Post(v) = &post_search[2] {
+      assert_eq!(data.timmy_post.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Filtered by id
+    let post_search_by_community = SearchCombinedQuery {
+      community_id: Some(data.community.id),
+      type_: Some(SearchType::Posts),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(2, post_search_by_community);
+
+    // Using a term
+    let post_search_by_name = SearchCombinedQuery {
+      search_term: Some("sara".into()),
+      type_: Some(SearchType::Posts),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(1, post_search_by_name);
+
+    // Test title only search to make sure 'postbody' doesn't show up
+    // Using a term
+    let post_search_title_only = SearchCombinedQuery {
+      search_term: Some("postbody".into()),
+      type_: Some(SearchType::Posts),
+      title_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert!(post_search_title_only.is_empty());
+
+    // Test title only search to make sure 'postbody' doesn't show up
+    // Using a term
+    let post_search_url_only = SearchCombinedQuery {
+      search_term: data.timmy_post.url.as_ref().map(ToString::to_string),
+      type_: Some(SearchType::Posts),
+      post_url_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(1, post_search_url_only);
+
+    // Liked / disliked only
+    let post_search_liked_only = SearchCombinedQuery {
+      type_: Some(SearchType::Posts),
+      liked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    // Should only be 1 not 2, because liked only ignores your own content
+    assert_length!(1, post_search_liked_only);
+
+    let post_search_disliked_only = SearchCombinedQuery {
+      type_: Some(SearchType::Posts),
+      disliked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    // Should be zero because you disliked your own post
+    assert_length!(0, post_search_disliked_only);
+
+    // Test top sort
+    let post_search_sort_top = SearchCombinedQuery {
+      type_: Some(SearchType::Posts),
+      sort: Some(SearchSortType::Top),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(3, post_search_sort_top);
+
+    // Timmy_post_2 has a dislike, so it should be last
+    if let SearchCombinedView::Post(v) = &post_search_sort_top[2] {
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    cleanup(data, pool).await?;
+
+    Ok(())
+  }
+
+  #[tokio::test]
+  #[serial]
+  async fn comment() -> LemmyResult<()> {
+    let pool = &build_db_pool_for_tests();
+    let pool = &mut pool.into();
+    let data = init_data(pool).await?;
+
+    // comment search
+    let comment_search = SearchCombinedQuery {
+      type_: Some(SearchType::Comments),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(3, comment_search);
+
+    // Make sure the types are correct
+    if let SearchCombinedView::Comment(v) = &comment_search[0] {
+      assert_eq!(data.sara_comment_2.id, v.comment.id);
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Comment(v) = &comment_search[1] {
+      assert_eq!(data.sara_comment.id, v.comment.id);
+      assert_eq!(data.sara_post.id, v.post.id);
+      assert_eq!(data.community_2.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    if let SearchCombinedView::Comment(v) = &comment_search[2] {
+      assert_eq!(data.timmy_comment.id, v.comment.id);
+      assert_eq!(data.timmy_post.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    // Filtered by id
+    let comment_search_by_community = SearchCombinedQuery {
+      community_id: Some(data.community.id),
+      type_: Some(SearchType::Comments),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(2, comment_search_by_community);
+
+    // Using a term
+    let comment_search_by_name = SearchCombinedQuery {
+      search_term: Some("gold".into()),
+      type_: Some(SearchType::Comments),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+
+    assert_length!(2, comment_search_by_name);
+
+    // Liked / disliked only
+    let comment_search_liked_only = SearchCombinedQuery {
+      type_: Some(SearchType::Comments),
+      liked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    assert_length!(1, comment_search_liked_only);
+
+    let comment_search_disliked_only = SearchCombinedQuery {
+      type_: Some(SearchType::Comments),
+      disliked_only: Some(true),
+      ..Default::default()
+    }
+    .list(pool, &Some(data.timmy_view.clone()))
+    .await?;
+
+    assert_length!(1, comment_search_disliked_only);
+
+    // Test top sort
+    let comment_search_sort_top = SearchCombinedQuery {
+      type_: Some(SearchType::Comments),
+      sort: Some(SearchSortType::Top),
+      ..Default::default()
+    }
+    .list(pool, &None)
+    .await?;
+    assert_length!(3, comment_search_sort_top);
+
+    // Sara comment 2 is disliked, so should be last
+    if let SearchCombinedView::Comment(v) = &comment_search_sort_top[2] {
+      assert_eq!(data.sara_comment_2.id, v.comment.id);
+      assert_eq!(data.timmy_post_2.id, v.post.id);
+      assert_eq!(data.community.id, v.community.id);
+    } else {
+      panic!("wrong type");
+    }
+
+    cleanup(data, pool).await?;
+
+    Ok(())
+  }
+}
diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment/comment_view.rs
similarity index 99%
rename from crates/db_views/src/comment_view.rs
rename to crates/db_views/src/comment/comment_view.rs
index 28e1f91bed..13a849449a 100644
--- a/crates/db_views/src/comment_view.rs
+++ b/crates/db_views/src/comment/comment_view.rs
@@ -374,7 +374,7 @@ fn handle_deleted(mut c: CommentView, is_admin: bool) -> CommentView {
 mod tests {
 
   use crate::{
-    comment_view::{CommentQuery, CommentSortType, CommentView, DbPool},
+    comment::comment_view::{CommentQuery, CommentSortType, CommentView, DbPool},
     structs::LocalUserView,
   };
   use lemmy_db_schema::{
diff --git a/crates/db_views/src/comment/mod.rs b/crates/db_views/src/comment/mod.rs
new file mode 100644
index 0000000000..b6f4b0b75d
--- /dev/null
+++ b/crates/db_views/src/comment/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "full")]
+pub mod comment_view;
diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views/src/community/community_follower_view.rs
similarity index 100%
rename from crates/db_views_actor/src/community_follower_view.rs
rename to crates/db_views/src/community/community_follower_view.rs
diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views/src/community/community_moderator_view.rs
similarity index 100%
rename from crates/db_views_actor/src/community_moderator_view.rs
rename to crates/db_views/src/community/community_moderator_view.rs
diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views/src/community/community_person_ban_view.rs
similarity index 100%
rename from crates/db_views_actor/src/community_person_ban_view.rs
rename to crates/db_views/src/community/community_person_ban_view.rs
diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views/src/community/community_view.rs
similarity index 99%
rename from crates/db_views_actor/src/community_view.rs
rename to crates/db_views/src/community/community_view.rs
index 1a8e3c4cd7..7ef746e539 100644
--- a/crates/db_views_actor/src/community_view.rs
+++ b/crates/db_views/src/community/community_view.rs
@@ -266,7 +266,7 @@ impl CommunityQuery<'_> {
 mod tests {
 
   use crate::{
-    community_view::CommunityQuery,
+    community::community_view::CommunityQuery,
     structs::{CommunitySortType, CommunityView},
   };
   use lemmy_db_schema::{
diff --git a/crates/db_views_actor/src/lib.rs b/crates/db_views/src/community/mod.rs
similarity index 56%
rename from crates/db_views_actor/src/lib.rs
rename to crates/db_views/src/community/mod.rs
index 3f3991734b..53d55c3cfb 100644
--- a/crates/db_views_actor/src/lib.rs
+++ b/crates/db_views/src/community/mod.rs
@@ -6,10 +6,3 @@ pub mod community_moderator_view;
 pub mod community_person_ban_view;
 #[cfg(feature = "full")]
 pub mod community_view;
-#[cfg(feature = "full")]
-pub mod inbox_combined_view;
-#[cfg(feature = "full")]
-pub mod person_view;
-#[cfg(feature = "full")]
-pub mod private_message_view;
-pub mod structs;
diff --git a/crates/db_views/src/lib.rs b/crates/db_views/src/lib.rs
index 74a1cd5c61..594303fda9 100644
--- a/crates/db_views/src/lib.rs
+++ b/crates/db_views/src/lib.rs
@@ -2,33 +2,23 @@
 extern crate serial_test;
 
 #[cfg(feature = "full")]
-pub mod comment_report_view;
+pub mod combined;
 #[cfg(feature = "full")]
-pub mod comment_view;
+pub mod comment;
 #[cfg(feature = "full")]
-pub mod custom_emoji_view;
+pub mod community;
 #[cfg(feature = "full")]
-pub mod local_image_view;
+pub mod local_user;
 #[cfg(feature = "full")]
-pub mod local_user_view;
+pub mod person;
 #[cfg(feature = "full")]
-pub mod person_content_combined_view;
+pub mod post;
 #[cfg(feature = "full")]
-pub mod person_saved_combined_view;
+pub mod private_message;
 #[cfg(feature = "full")]
-pub mod post_report_view;
+pub mod registration_applications;
 #[cfg(feature = "full")]
-pub mod post_tags_view;
+pub mod reports;
 #[cfg(feature = "full")]
-pub mod post_view;
-#[cfg(feature = "full")]
-pub mod private_message_report_view;
-#[cfg(feature = "full")]
-pub mod registration_application_view;
-#[cfg(feature = "full")]
-pub mod report_combined_view;
-#[cfg(feature = "full")]
-pub mod site_view;
+pub mod site;
 pub mod structs;
-#[cfg(feature = "full")]
-pub mod vote_view;
diff --git a/crates/db_views/src/local_user_view.rs b/crates/db_views/src/local_user/local_user_view.rs
similarity index 100%
rename from crates/db_views/src/local_user_view.rs
rename to crates/db_views/src/local_user/local_user_view.rs
diff --git a/crates/db_views/src/local_user/mod.rs b/crates/db_views/src/local_user/mod.rs
new file mode 100644
index 0000000000..ffe33f1385
--- /dev/null
+++ b/crates/db_views/src/local_user/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "full")]
+pub mod local_user_view;
diff --git a/crates/db_views/src/person/mod.rs b/crates/db_views/src/person/mod.rs
new file mode 100644
index 0000000000..5feb31647f
--- /dev/null
+++ b/crates/db_views/src/person/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "full")]
+pub mod person_view;
diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views/src/person/person_view.rs
similarity index 100%
rename from crates/db_views_actor/src/person_view.rs
rename to crates/db_views/src/person/person_view.rs
diff --git a/crates/db_views/src/post/mod.rs b/crates/db_views/src/post/mod.rs
new file mode 100644
index 0000000000..fec40b0951
--- /dev/null
+++ b/crates/db_views/src/post/mod.rs
@@ -0,0 +1,4 @@
+#[cfg(feature = "full")]
+pub mod post_tags_view;
+#[cfg(feature = "full")]
+pub mod post_view;
diff --git a/crates/db_views/src/post_tags_view.rs b/crates/db_views/src/post/post_tags_view.rs
similarity index 100%
rename from crates/db_views/src/post_tags_view.rs
rename to crates/db_views/src/post/post_tags_view.rs
diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post/post_view.rs
similarity index 99%
rename from crates/db_views/src/post_view.rs
rename to crates/db_views/src/post/post_view.rs
index f04747cfdc..7065b46377 100644
--- a/crates/db_views/src/post_view.rs
+++ b/crates/db_views/src/post/post_view.rs
@@ -638,7 +638,7 @@ impl<'a> PostQuery<'a> {
 #[cfg(test)]
 mod tests {
   use crate::{
-    post_view::{PaginationCursorData, PostQuery, PostView},
+    post::post_view::{PaginationCursorData, PostQuery, PostView},
     structs::{LocalUserView, PostTags},
   };
   use chrono::Utc;
@@ -1604,7 +1604,7 @@ mod tests {
 
       listed_post_ids.extend(post_listings.iter().map(|p| p.post.id));
 
-      if let Some(p) = post_listings.into_iter().last() {
+      if let Some(p) = post_listings.into_iter().next_back() {
         page_after = Some(p.counts);
       } else {
         break;
diff --git a/crates/db_views/src/private_message/mod.rs b/crates/db_views/src/private_message/mod.rs
new file mode 100644
index 0000000000..71bde5dd19
--- /dev/null
+++ b/crates/db_views/src/private_message/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "full")]
+pub mod private_message_view;
diff --git a/crates/db_views_actor/src/private_message_view.rs b/crates/db_views/src/private_message/private_message_view.rs
similarity index 100%
rename from crates/db_views_actor/src/private_message_view.rs
rename to crates/db_views/src/private_message/private_message_view.rs
diff --git a/crates/db_views/src/registration_applications/mod.rs b/crates/db_views/src/registration_applications/mod.rs
new file mode 100644
index 0000000000..b71cca9706
--- /dev/null
+++ b/crates/db_views/src/registration_applications/mod.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "full")]
+pub mod registration_application_view;
diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_applications/registration_application_view.rs
similarity index 99%
rename from crates/db_views/src/registration_application_view.rs
rename to crates/db_views/src/registration_applications/registration_application_view.rs
index 87bbd5b01f..cdb6af61dc 100644
--- a/crates/db_views/src/registration_application_view.rs
+++ b/crates/db_views/src/registration_applications/registration_application_view.rs
@@ -137,7 +137,7 @@ impl RegistrationApplicationQuery {
 #[cfg(test)]
 mod tests {
 
-  use crate::registration_application_view::{
+  use crate::registration_applications::registration_application_view::{
     RegistrationApplicationQuery,
     RegistrationApplicationView,
   };
diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/reports/comment_report_view.rs
similarity index 100%
rename from crates/db_views/src/comment_report_view.rs
rename to crates/db_views/src/reports/comment_report_view.rs
diff --git a/crates/db_views/src/reports/mod.rs b/crates/db_views/src/reports/mod.rs
new file mode 100644
index 0000000000..aa42ff6e91
--- /dev/null
+++ b/crates/db_views/src/reports/mod.rs
@@ -0,0 +1,6 @@
+#[cfg(feature = "full")]
+pub mod comment_report_view;
+#[cfg(feature = "full")]
+pub mod post_report_view;
+#[cfg(feature = "full")]
+pub mod private_message_report_view;
diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/reports/post_report_view.rs
similarity index 100%
rename from crates/db_views/src/post_report_view.rs
rename to crates/db_views/src/reports/post_report_view.rs
diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/reports/private_message_report_view.rs
similarity index 100%
rename from crates/db_views/src/private_message_report_view.rs
rename to crates/db_views/src/reports/private_message_report_view.rs
diff --git a/crates/db_views/src/custom_emoji_view.rs b/crates/db_views/src/site/custom_emoji_view.rs
similarity index 100%
rename from crates/db_views/src/custom_emoji_view.rs
rename to crates/db_views/src/site/custom_emoji_view.rs
diff --git a/crates/db_views/src/local_image_view.rs b/crates/db_views/src/site/local_image_view.rs
similarity index 100%
rename from crates/db_views/src/local_image_view.rs
rename to crates/db_views/src/site/local_image_view.rs
diff --git a/crates/db_views/src/site/mod.rs b/crates/db_views/src/site/mod.rs
new file mode 100644
index 0000000000..f2c318409d
--- /dev/null
+++ b/crates/db_views/src/site/mod.rs
@@ -0,0 +1,8 @@
+#[cfg(feature = "full")]
+pub mod custom_emoji_view;
+#[cfg(feature = "full")]
+pub mod local_image_view;
+#[cfg(feature = "full")]
+pub mod site_view;
+#[cfg(feature = "full")]
+pub mod vote_view;
diff --git a/crates/db_views/src/site_view.rs b/crates/db_views/src/site/site_view.rs
similarity index 100%
rename from crates/db_views/src/site_view.rs
rename to crates/db_views/src/site/site_view.rs
diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/site/vote_view.rs
similarity index 100%
rename from crates/db_views/src/vote_view.rs
rename to crates/db_views/src/site/vote_view.rs
diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs
index 43eba36c72..09a868be9f 100644
--- a/crates/db_views/src/structs.rs
+++ b/crates/db_views/src/structs.rs
@@ -12,17 +12,44 @@ use lemmy_db_schema::{
   },
   source::{
     comment::Comment,
+    comment_reply::CommentReply,
     comment_report::CommentReport,
     community::Community,
     community_report::CommunityReport,
     custom_emoji::CustomEmoji,
     custom_emoji_keyword::CustomEmojiKeyword,
     images::{ImageDetails, LocalImage},
+    instance::Instance,
     local_site::LocalSite,
     local_site_rate_limit::LocalSiteRateLimit,
     local_user::LocalUser,
     local_user_vote_display_mode::LocalUserVoteDisplayMode,
+    mod_log::{
+      admin::{
+        AdminAllowInstance,
+        AdminBlockInstance,
+        AdminPurgeComment,
+        AdminPurgeCommunity,
+        AdminPurgePerson,
+        AdminPurgePost,
+      },
+      moderator::{
+        ModAdd,
+        ModAddCommunity,
+        ModBan,
+        ModBanFromCommunity,
+        ModFeaturePost,
+        ModHideCommunity,
+        ModLockPost,
+        ModRemoveComment,
+        ModRemoveCommunity,
+        ModRemovePost,
+        ModTransferCommunity,
+      },
+    },
     person::Person,
+    person_comment_mention::PersonCommentMention,
+    person_post_mention::PersonPostMention,
     post::Post,
     post_report::PostReport,
     private_message::PrivateMessage,
@@ -347,7 +374,7 @@ pub enum ReportCombinedView {
 #[cfg_attr(feature = "full", derive(Queryable))]
 #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
 /// A combined person_content view
-pub struct PersonContentViewInternal {
+pub struct PersonContentCombinedViewInternal {
   // Post-specific
   pub post_counts: PostAggregates,
   pub post_unread_comments: i64,
@@ -384,6 +411,596 @@ pub enum PersonContentCombinedView {
   Comment(CommentView),
 }
 
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A community follower.
+pub struct CommunityFollowerView {
+  pub community: Community,
+  pub follower: Person,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A community moderator.
+pub struct CommunityModeratorView {
+  pub community: Community,
+  pub moderator: Person,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+/// A community person ban.
+pub struct CommunityPersonBanView {
+  pub community: Community,
+  pub person: Person,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A community view.
+pub struct CommunityView {
+  pub community: Community,
+  pub subscribed: SubscribedType,
+  pub blocked: bool,
+  pub counts: CommunityAggregates,
+  pub banned_from_community: bool,
+}
+
+/// The community sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+pub enum CommunitySortType {
+  #[default]
+  Active,
+  Hot,
+  New,
+  Old,
+  TopDay,
+  TopWeek,
+  TopMonth,
+  TopYear,
+  TopAll,
+  MostComments,
+  NewComments,
+  TopHour,
+  TopSixHour,
+  TopTwelveHour,
+  TopThreeMonths,
+  TopSixMonths,
+  TopNineMonths,
+  Controversial,
+  Scaled,
+  NameAsc,
+  NameDesc,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A person comment mention view.
+pub struct PersonCommentMentionView {
+  pub person_comment_mention: PersonCommentMention,
+  pub comment: Comment,
+  pub creator: Person,
+  pub post: Post,
+  pub community: Community,
+  pub recipient: Person,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool,
+  pub banned_from_community: bool,
+  pub creator_is_moderator: bool,
+  pub creator_is_admin: bool,
+  pub subscribed: SubscribedType,
+  pub saved: bool,
+  pub creator_blocked: bool,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub my_vote: Option<i16>,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A person post mention view.
+pub struct PersonPostMentionView {
+  pub person_post_mention: PersonPostMention,
+  pub post: Post,
+  pub creator: Person,
+  pub community: Community,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub image_details: Option<ImageDetails>,
+  pub recipient: Person,
+  pub counts: PostAggregates,
+  pub creator_banned_from_community: bool,
+  pub banned_from_community: bool,
+  pub creator_is_moderator: bool,
+  pub creator_is_admin: bool,
+  pub subscribed: SubscribedType,
+  pub saved: bool,
+  pub read: bool,
+  pub hidden: bool,
+  pub creator_blocked: bool,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub my_vote: Option<i16>,
+  pub unread_comments: i64,
+  pub post_tags: PostTags,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A comment reply view.
+pub struct CommentReplyView {
+  pub comment_reply: CommentReply,
+  pub comment: Comment,
+  pub creator: Person,
+  pub post: Post,
+  pub community: Community,
+  pub recipient: Person,
+  pub counts: CommentAggregates,
+  pub creator_banned_from_community: bool,
+  pub banned_from_community: bool,
+  pub creator_is_moderator: bool,
+  pub creator_is_admin: bool,
+  pub subscribed: SubscribedType,
+  pub saved: bool,
+  pub creator_blocked: bool,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub my_vote: Option<i16>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A person view.
+pub struct PersonView {
+  pub person: Person,
+  pub counts: PersonAggregates,
+  pub is_admin: bool,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+pub struct PendingFollow {
+  pub person: Person,
+  pub community: Community,
+  pub is_new_instance: bool,
+  pub subscribed: SubscribedType,
+}
+
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A private message view.
+pub struct PrivateMessageView {
+  pub private_message: PrivateMessage,
+  pub creator: Person,
+  pub recipient: Person,
+}
+
+/// like PaginationCursor but for the report_combined table
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+pub struct InboxCombinedPaginationCursor(pub String);
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+/// A combined inbox view
+pub struct InboxCombinedViewInternal {
+  // Comment reply
+  pub comment_reply: Option<CommentReply>,
+  // Person comment mention
+  pub person_comment_mention: Option<PersonCommentMention>,
+  // Person post mention
+  pub person_post_mention: Option<PersonPostMention>,
+  pub post_counts: Option<PostAggregates>,
+  pub post_unread_comments: Option<i64>,
+  pub post_saved: bool,
+  pub post_read: bool,
+  pub post_hidden: bool,
+  pub my_post_vote: Option<i16>,
+  pub image_details: Option<ImageDetails>,
+  pub post_tags: PostTags,
+  // Private message
+  pub private_message: Option<PrivateMessage>,
+  // Shared
+  pub post: Option<Post>,
+  pub community: Option<Community>,
+  pub comment: Option<Comment>,
+  pub comment_counts: Option<CommentAggregates>,
+  pub comment_saved: bool,
+  pub my_comment_vote: Option<i16>,
+  pub subscribed: SubscribedType,
+  pub item_creator: Person,
+  pub item_recipient: Person,
+  pub item_creator_is_admin: bool,
+  pub item_creator_is_moderator: bool,
+  pub item_creator_banned_from_community: bool,
+  pub item_creator_blocked: bool,
+  pub banned_from_community: bool,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+// Use serde's internal tagging, to work easier with javascript libraries
+#[serde(tag = "type_")]
+pub enum InboxCombinedView {
+  CommentReply(CommentReplyView),
+  CommentMention(PersonCommentMentionView),
+  PostMention(PersonPostMentionView),
+  PrivateMessage(PrivateMessageView),
+}
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When someone is added as a community moderator.
+pub struct ModAddCommunityView {
+  pub mod_add_community: ModAddCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub community: Community,
+  pub other_person: Person,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When someone is added as a site moderator.
+pub struct ModAddView {
+  pub mod_add: ModAdd,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When someone is banned from a community.
+pub struct ModBanFromCommunityView {
+  pub mod_ban_from_community: ModBanFromCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub community: Community,
+  pub other_person: Person,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When someone is banned from the site.
+pub struct ModBanView {
+  pub mod_ban: ModBan,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a community is hidden from public view.
+pub struct ModHideCommunityView {
+  pub mod_hide_community: ModHideCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator locks a post (prevents new comments being made).
+pub struct ModLockPostView {
+  pub mod_lock_post: ModLockPost,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+  pub post: Post,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator removes a comment.
+pub struct ModRemoveCommentView {
+  pub mod_remove_comment: ModRemoveComment,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+  pub comment: Comment,
+  pub post: Post,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator removes a community.
+pub struct ModRemoveCommunityView {
+  pub mod_remove_community: ModRemoveCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator removes a post.
+pub struct ModRemovePostView {
+  pub mod_remove_post: ModRemovePost,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+  pub post: Post,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator features a post on a community (pins it to the top).
+pub struct ModFeaturePostView {
+  pub mod_feature_post: ModFeaturePost,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub other_person: Person,
+  pub post: Post,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When a moderator transfers a community to a new owner.
+pub struct ModTransferCommunityView {
+  pub mod_transfer_community: ModTransferCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub moderator: Option<Person>,
+  pub community: Community,
+  pub other_person: Person,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a comment.
+pub struct AdminPurgeCommentView {
+  pub admin_purge_comment: AdminPurgeComment,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+  pub post: Post,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a community.
+pub struct AdminPurgeCommunityView {
+  pub admin_purge_community: AdminPurgeCommunity,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a person.
+pub struct AdminPurgePersonView {
+  pub admin_purge_person: AdminPurgePerson,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a post.
+pub struct AdminPurgePostView {
+  pub admin_purge_post: AdminPurgePost,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+  pub community: Community,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a post.
+pub struct AdminBlockInstanceView {
+  pub admin_block_instance: AdminBlockInstance,
+  pub instance: Instance,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+}
+
+#[skip_serializing_none]
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS, Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+#[cfg_attr(feature = "full", ts(export))]
+/// When an admin purges a post.
+pub struct AdminAllowInstanceView {
+  pub admin_allow_instance: AdminAllowInstance,
+  pub instance: Instance,
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub admin: Option<Person>,
+}
+
+/// like PaginationCursor but for the modlog_combined
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+pub struct ModlogCombinedPaginationCursor(pub String);
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+/// A combined modlog view
+pub struct ModlogCombinedViewInternal {
+  // Specific
+  pub admin_allow_instance: Option<AdminAllowInstance>,
+  pub admin_block_instance: Option<AdminBlockInstance>,
+  pub admin_purge_comment: Option<AdminPurgeComment>,
+  pub admin_purge_community: Option<AdminPurgeCommunity>,
+  pub admin_purge_person: Option<AdminPurgePerson>,
+  pub admin_purge_post: Option<AdminPurgePost>,
+  pub mod_add: Option<ModAdd>,
+  pub mod_add_community: Option<ModAddCommunity>,
+  pub mod_ban: Option<ModBan>,
+  pub mod_ban_from_community: Option<ModBanFromCommunity>,
+  pub mod_feature_post: Option<ModFeaturePost>,
+  pub mod_hide_community: Option<ModHideCommunity>,
+  pub mod_lock_post: Option<ModLockPost>,
+  pub mod_remove_comment: Option<ModRemoveComment>,
+  pub mod_remove_community: Option<ModRemoveCommunity>,
+  pub mod_remove_post: Option<ModRemovePost>,
+  pub mod_transfer_community: Option<ModTransferCommunity>,
+  // Specific fields
+
+  // Shared
+  pub moderator: Option<Person>,
+  pub other_person: Option<Person>,
+  pub instance: Option<Instance>,
+  pub community: Option<Community>,
+  pub post: Option<Post>,
+  pub comment: Option<Comment>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+// Use serde's internal tagging, to work easier with javascript libraries
+#[serde(tag = "type_")]
+pub enum ModlogCombinedView {
+  AdminAllowInstance(AdminAllowInstanceView),
+  AdminBlockInstance(AdminBlockInstanceView),
+  AdminPurgeComment(AdminPurgeCommentView),
+  AdminPurgeCommunity(AdminPurgeCommunityView),
+  AdminPurgePerson(AdminPurgePersonView),
+  AdminPurgePost(AdminPurgePostView),
+  ModAdd(ModAddView),
+  ModAddCommunity(ModAddCommunityView),
+  ModBan(ModBanView),
+  ModBanFromCommunity(ModBanFromCommunityView),
+  ModFeaturePost(ModFeaturePostView),
+  ModHideCommunity(ModHideCommunityView),
+  ModLockPost(ModLockPostView),
+  ModRemoveComment(ModRemoveCommentView),
+  ModRemoveCommunity(ModRemoveCommunityView),
+  ModRemovePost(ModRemovePostView),
+  ModTransferCommunity(ModTransferCommunityView),
+}
+
+/// like PaginationCursor but for the modlog_combined
+// TODO get rid of all these pagination cursors
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+pub struct SearchCombinedPaginationCursor(pub String);
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(Queryable))]
+#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
+/// A combined search view
+pub struct SearchCombinedViewInternal {
+  // Post-specific
+  pub post: Option<Post>,
+  pub post_counts: Option<PostAggregates>,
+  pub post_unread_comments: Option<i64>,
+  pub post_saved: bool,
+  pub post_read: bool,
+  pub post_hidden: bool,
+  pub my_post_vote: Option<i16>,
+  pub image_details: Option<ImageDetails>,
+  pub post_tags: PostTags,
+  // // Comment-specific
+  pub comment: Option<Comment>,
+  pub comment_counts: Option<CommentAggregates>,
+  pub comment_saved: bool,
+  pub my_comment_vote: Option<i16>,
+  // // Community-specific
+  pub community: Option<Community>,
+  pub community_counts: Option<CommunityAggregates>,
+  pub community_blocked: bool,
+  pub subscribed: SubscribedType,
+  // Person
+  pub item_creator_counts: Option<PersonAggregates>,
+  // Shared
+  pub item_creator: Option<Person>,
+  pub item_creator_is_admin: bool,
+  pub item_creator_is_moderator: bool,
+  pub item_creator_banned_from_community: bool,
+  pub item_creator_blocked: bool,
+  pub banned_from_community: bool,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+// Use serde's internal tagging, to work easier with javascript libraries
+#[serde(tag = "type_")]
+pub enum SearchCombinedView {
+  Post(PostView),
+  Comment(CommentView),
+  Community(CommunityView),
+  Person(PersonView),
+}
+
 #[derive(Clone, serde::Serialize, serde::Deserialize, Debug, PartialEq, Default)]
 #[cfg_attr(feature = "full", derive(TS, FromSqlRow, AsExpression))]
 #[serde(transparent)]
diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml
deleted file mode 100644
index 34d64d0b01..0000000000
--- a/crates/db_views_actor/Cargo.toml
+++ /dev/null
@@ -1,50 +0,0 @@
-[package]
-name = "lemmy_db_views_actor"
-version.workspace = true
-edition.workspace = true
-description.workspace = true
-license.workspace = true
-homepage.workspace = true
-documentation.workspace = true
-repository.workspace = true
-
-[lib]
-doctest = false
-
-[lints]
-workspace = true
-
-[features]
-full = [
-  "lemmy_db_schema/full",
-  "lemmy_utils/full",
-  "i-love-jesus",
-  "diesel",
-  "diesel-async",
-  "ts-rs",
-]
-
-[dependencies]
-lemmy_db_schema = { workspace = true }
-diesel = { workspace = true, features = [
-  "chrono",
-  "postgres",
-  "serde_json",
-], optional = true }
-diesel-async = { workspace = true, features = [
-  "deadpool",
-  "postgres",
-], optional = true }
-serde = { workspace = true }
-serde_with = { workspace = true }
-ts-rs = { workspace = true, optional = true }
-chrono.workspace = true
-strum = { workspace = true }
-lemmy_utils = { workspace = true, optional = true }
-i-love-jesus = { workspace = true, optional = true }
-
-[dev-dependencies]
-serial_test = { workspace = true }
-tokio = { workspace = true }
-pretty_assertions = { workspace = true }
-url.workspace = true
diff --git a/crates/db_views_actor/src/structs.rs b/crates/db_views_actor/src/structs.rs
deleted file mode 100644
index b1f75c86d2..0000000000
--- a/crates/db_views_actor/src/structs.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-#[cfg(feature = "full")]
-use diesel::Queryable;
-use lemmy_db_schema::{
-  aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates, PostAggregates},
-  source::{
-    comment::Comment,
-    comment_reply::CommentReply,
-    community::Community,
-    images::ImageDetails,
-    person::Person,
-    person_comment_mention::PersonCommentMention,
-    person_post_mention::PersonPostMention,
-    post::Post,
-    private_message::PrivateMessage,
-  },
-  SubscribedType,
-};
-use serde::{Deserialize, Serialize};
-use serde_with::skip_serializing_none;
-#[cfg(feature = "full")]
-use ts_rs::TS;
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A community follower.
-pub struct CommunityFollowerView {
-  pub community: Community,
-  pub follower: Person,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A community moderator.
-pub struct CommunityModeratorView {
-  pub community: Community,
-  pub moderator: Person,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-/// A community person ban.
-pub struct CommunityPersonBanView {
-  pub community: Community,
-  pub person: Person,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A community view.
-pub struct CommunityView {
-  pub community: Community,
-  pub subscribed: SubscribedType,
-  pub blocked: bool,
-  pub counts: CommunityAggregates,
-  pub banned_from_community: bool,
-}
-
-/// The community sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
-#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "full", derive(TS))]
-#[cfg_attr(feature = "full", ts(export))]
-pub enum CommunitySortType {
-  #[default]
-  Active,
-  Hot,
-  New,
-  Old,
-  TopDay,
-  TopWeek,
-  TopMonth,
-  TopYear,
-  TopAll,
-  MostComments,
-  NewComments,
-  TopHour,
-  TopSixHour,
-  TopTwelveHour,
-  TopThreeMonths,
-  TopSixMonths,
-  TopNineMonths,
-  Controversial,
-  Scaled,
-  NameAsc,
-  NameDesc,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A person comment mention view.
-pub struct PersonCommentMentionView {
-  pub person_comment_mention: PersonCommentMention,
-  pub comment: Comment,
-  pub creator: Person,
-  pub post: Post,
-  pub community: Community,
-  pub recipient: Person,
-  pub counts: CommentAggregates,
-  pub creator_banned_from_community: bool,
-  pub banned_from_community: bool,
-  pub creator_is_moderator: bool,
-  pub creator_is_admin: bool,
-  pub subscribed: SubscribedType,
-  pub saved: bool,
-  pub creator_blocked: bool,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub my_vote: Option<i16>,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A person post mention view.
-pub struct PersonPostMentionView {
-  pub person_post_mention: PersonPostMention,
-  pub post: Post,
-  pub creator: Person,
-  pub community: Community,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub image_details: Option<ImageDetails>,
-  pub recipient: Person,
-  pub counts: PostAggregates,
-  pub creator_banned_from_community: bool,
-  pub banned_from_community: bool,
-  pub creator_is_moderator: bool,
-  pub creator_is_admin: bool,
-  pub subscribed: SubscribedType,
-  pub saved: bool,
-  pub read: bool,
-  pub hidden: bool,
-  pub creator_blocked: bool,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub my_vote: Option<i16>,
-  pub unread_comments: i64,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A comment reply view.
-pub struct CommentReplyView {
-  pub comment_reply: CommentReply,
-  pub comment: Comment,
-  pub creator: Person,
-  pub post: Post,
-  pub community: Community,
-  pub recipient: Person,
-  pub counts: CommentAggregates,
-  pub creator_banned_from_community: bool,
-  pub banned_from_community: bool,
-  pub creator_is_moderator: bool,
-  pub creator_is_admin: bool,
-  pub subscribed: SubscribedType,
-  pub saved: bool,
-  pub creator_blocked: bool,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub my_vote: Option<i16>,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A person view.
-pub struct PersonView {
-  pub person: Person,
-  pub counts: PersonAggregates,
-  pub is_admin: bool,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-pub struct PendingFollow {
-  pub person: Person,
-  pub community: Community,
-  pub is_new_instance: bool,
-  pub subscribed: SubscribedType,
-}
-
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// A private message view.
-pub struct PrivateMessageView {
-  pub private_message: PrivateMessage,
-  pub creator: Person,
-  pub recipient: Person,
-}
-
-/// like PaginationCursor but for the report_combined table
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "full", derive(TS))]
-#[cfg_attr(feature = "full", ts(export))]
-pub struct InboxCombinedPaginationCursor(pub String);
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-/// A combined inbox view
-pub struct InboxCombinedViewInternal {
-  // Comment reply
-  pub comment_reply: Option<CommentReply>,
-  // Person comment mention
-  pub person_comment_mention: Option<PersonCommentMention>,
-  // Person post mention
-  pub person_post_mention: Option<PersonPostMention>,
-  pub post_counts: Option<PostAggregates>,
-  pub post_unread_comments: Option<i64>,
-  pub post_saved: bool,
-  pub post_read: bool,
-  pub post_hidden: bool,
-  pub my_post_vote: Option<i16>,
-  pub image_details: Option<ImageDetails>,
-  // Private message
-  pub private_message: Option<PrivateMessage>,
-  // Shared
-  pub post: Option<Post>,
-  pub community: Option<Community>,
-  pub comment: Option<Comment>,
-  pub comment_counts: Option<CommentAggregates>,
-  pub comment_saved: bool,
-  pub my_comment_vote: Option<i16>,
-  pub subscribed: SubscribedType,
-  pub item_creator: Person,
-  pub item_recipient: Person,
-  pub item_creator_is_admin: bool,
-  pub item_creator_is_moderator: bool,
-  pub item_creator_banned_from_community: bool,
-  pub item_creator_blocked: bool,
-  pub banned_from_community: bool,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS))]
-#[cfg_attr(feature = "full", ts(export))]
-// Use serde's internal tagging, to work easier with javascript libraries
-#[serde(tag = "type_")]
-pub enum InboxCombinedView {
-  CommentReply(CommentReplyView),
-  CommentMention(PersonCommentMentionView),
-  PostMention(PersonPostMentionView),
-  PrivateMessage(PrivateMessageView),
-}
diff --git a/crates/db_views_moderator/Cargo.toml b/crates/db_views_moderator/Cargo.toml
deleted file mode 100644
index a7257c4f16..0000000000
--- a/crates/db_views_moderator/Cargo.toml
+++ /dev/null
@@ -1,47 +0,0 @@
-[package]
-name = "lemmy_db_views_moderator"
-version.workspace = true
-edition.workspace = true
-description.workspace = true
-license.workspace = true
-homepage.workspace = true
-documentation.workspace = true
-repository.workspace = true
-
-[lib]
-doctest = false
-
-[lints]
-workspace = true
-
-[features]
-full = [
-  "lemmy_db_schema/full",
-  "lemmy_utils",
-  "i-love-jesus",
-  "diesel",
-  "diesel-async",
-  "ts-rs",
-]
-
-[dependencies]
-lemmy_db_schema = { workspace = true }
-lemmy_utils = { workspace = true, optional = true }
-i-love-jesus = { workspace = true, optional = true }
-diesel = { workspace = true, features = [
-  "chrono",
-  "postgres",
-  "serde_json",
-], optional = true }
-diesel-async = { workspace = true, features = [
-  "deadpool",
-  "postgres",
-], optional = true }
-serde = { workspace = true }
-serde_with = { workspace = true }
-ts-rs = { workspace = true, optional = true }
-
-[dev-dependencies]
-serial_test = { workspace = true }
-tokio = { workspace = true }
-pretty_assertions = { workspace = true }
diff --git a/crates/db_views_moderator/src/lib.rs b/crates/db_views_moderator/src/lib.rs
deleted file mode 100644
index 1cc21da279..0000000000
--- a/crates/db_views_moderator/src/lib.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-#[cfg(feature = "full")]
-pub mod modlog_combined_view;
-pub mod structs;
diff --git a/crates/db_views_moderator/src/structs.rs b/crates/db_views_moderator/src/structs.rs
deleted file mode 100644
index 513a797058..0000000000
--- a/crates/db_views_moderator/src/structs.rs
+++ /dev/null
@@ -1,332 +0,0 @@
-#[cfg(feature = "full")]
-use diesel::Queryable;
-use lemmy_db_schema::source::{
-  comment::Comment,
-  community::Community,
-  instance::Instance,
-  mod_log::{
-    admin::{
-      AdminAllowInstance,
-      AdminBlockInstance,
-      AdminPurgeComment,
-      AdminPurgeCommunity,
-      AdminPurgePerson,
-      AdminPurgePost,
-    },
-    moderator::{
-      ModAdd,
-      ModAddCommunity,
-      ModBan,
-      ModBanFromCommunity,
-      ModFeaturePost,
-      ModHideCommunity,
-      ModLockPost,
-      ModRemoveComment,
-      ModRemoveCommunity,
-      ModRemovePost,
-      ModTransferCommunity,
-    },
-  },
-  person::Person,
-  post::Post,
-};
-use serde::{Deserialize, Serialize};
-use serde_with::skip_serializing_none;
-#[cfg(feature = "full")]
-use ts_rs::TS;
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When someone is added as a community moderator.
-pub struct ModAddCommunityView {
-  pub mod_add_community: ModAddCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub community: Community,
-  pub other_person: Person,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When someone is added as a site moderator.
-pub struct ModAddView {
-  pub mod_add: ModAdd,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When someone is banned from a community.
-pub struct ModBanFromCommunityView {
-  pub mod_ban_from_community: ModBanFromCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub community: Community,
-  pub other_person: Person,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When someone is banned from the site.
-pub struct ModBanView {
-  pub mod_ban: ModBan,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a community is hidden from public view.
-pub struct ModHideCommunityView {
-  pub mod_hide_community: ModHideCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator locks a post (prevents new comments being made).
-pub struct ModLockPostView {
-  pub mod_lock_post: ModLockPost,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-  pub post: Post,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator removes a comment.
-pub struct ModRemoveCommentView {
-  pub mod_remove_comment: ModRemoveComment,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-  pub comment: Comment,
-  pub post: Post,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator removes a community.
-pub struct ModRemoveCommunityView {
-  pub mod_remove_community: ModRemoveCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator removes a post.
-pub struct ModRemovePostView {
-  pub mod_remove_post: ModRemovePost,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-  pub post: Post,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator features a post on a community (pins it to the top).
-pub struct ModFeaturePostView {
-  pub mod_feature_post: ModFeaturePost,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub other_person: Person,
-  pub post: Post,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When a moderator transfers a community to a new owner.
-pub struct ModTransferCommunityView {
-  pub mod_transfer_community: ModTransferCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub moderator: Option<Person>,
-  pub community: Community,
-  pub other_person: Person,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a comment.
-pub struct AdminPurgeCommentView {
-  pub admin_purge_comment: AdminPurgeComment,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-  pub post: Post,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a community.
-pub struct AdminPurgeCommunityView {
-  pub admin_purge_community: AdminPurgeCommunity,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a person.
-pub struct AdminPurgePersonView {
-  pub admin_purge_person: AdminPurgePerson,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a post.
-pub struct AdminPurgePostView {
-  pub admin_purge_post: AdminPurgePost,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-  pub community: Community,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a post.
-pub struct AdminBlockInstanceView {
-  pub admin_block_instance: AdminBlockInstance,
-  pub instance: Instance,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-}
-
-#[skip_serializing_none]
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS, Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-#[cfg_attr(feature = "full", ts(export))]
-/// When an admin purges a post.
-pub struct AdminAllowInstanceView {
-  pub admin_allow_instance: AdminAllowInstance,
-  pub instance: Instance,
-  #[cfg_attr(feature = "full", ts(optional))]
-  pub admin: Option<Person>,
-}
-
-/// like PaginationCursor but for the modlog_combined
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "full", derive(TS))]
-#[cfg_attr(feature = "full", ts(export))]
-pub struct ModlogCombinedPaginationCursor(pub String);
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(Queryable))]
-#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
-/// A combined modlog view
-pub struct ModlogCombinedViewInternal {
-  // Specific
-  pub admin_allow_instance: Option<AdminAllowInstance>,
-  pub admin_block_instance: Option<AdminBlockInstance>,
-  pub admin_purge_comment: Option<AdminPurgeComment>,
-  pub admin_purge_community: Option<AdminPurgeCommunity>,
-  pub admin_purge_person: Option<AdminPurgePerson>,
-  pub admin_purge_post: Option<AdminPurgePost>,
-  pub mod_add: Option<ModAdd>,
-  pub mod_add_community: Option<ModAddCommunity>,
-  pub mod_ban: Option<ModBan>,
-  pub mod_ban_from_community: Option<ModBanFromCommunity>,
-  pub mod_feature_post: Option<ModFeaturePost>,
-  pub mod_hide_community: Option<ModHideCommunity>,
-  pub mod_lock_post: Option<ModLockPost>,
-  pub mod_remove_comment: Option<ModRemoveComment>,
-  pub mod_remove_community: Option<ModRemoveCommunity>,
-  pub mod_remove_post: Option<ModRemovePost>,
-  pub mod_transfer_community: Option<ModTransferCommunity>,
-  // Specific fields
-
-  // Shared
-  pub moderator: Option<Person>,
-  pub other_person: Option<Person>,
-  pub instance: Option<Instance>,
-  pub community: Option<Community>,
-  pub post: Option<Post>,
-  pub comment: Option<Comment>,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-#[cfg_attr(feature = "full", derive(TS))]
-#[cfg_attr(feature = "full", ts(export))]
-// Use serde's internal tagging, to work easier with javascript libraries
-#[serde(tag = "type_")]
-pub enum ModlogCombinedView {
-  AdminAllowInstance(AdminAllowInstanceView),
-  AdminBlockInstance(AdminBlockInstanceView),
-  AdminPurgeComment(AdminPurgeCommentView),
-  AdminPurgeCommunity(AdminPurgeCommunityView),
-  AdminPurgePerson(AdminPurgePersonView),
-  AdminPurgePost(AdminPurgePostView),
-  ModAdd(ModAddView),
-  ModAddCommunity(ModAddCommunityView),
-  ModBan(ModBanView),
-  ModBanFromCommunity(ModBanFromCommunityView),
-  ModFeaturePost(ModFeaturePostView),
-  ModHideCommunity(ModHideCommunityView),
-  ModLockPost(ModLockPostView),
-  ModRemoveComment(ModRemoveCommentView),
-  ModRemoveCommunity(ModRemoveCommunityView),
-  ModRemovePost(ModRemovePostView),
-  ModTransferCommunity(ModTransferCommunityView),
-}
diff --git a/crates/federate/Cargo.toml b/crates/federate/Cargo.toml
index 7ea46de808..fb5e436b03 100644
--- a/crates/federate/Cargo.toml
+++ b/crates/federate/Cargo.toml
@@ -18,7 +18,7 @@ workspace = true
 lemmy_api_common.workspace = true
 lemmy_apub.workspace = true
 lemmy_db_schema = { workspace = true, features = ["full"] }
-lemmy_db_views_actor.workspace = true
+lemmy_db_views.workspace = true
 lemmy_utils.workspace = true
 
 activitypub_federation.workspace = true
diff --git a/crates/federate/src/inboxes.rs b/crates/federate/src/inboxes.rs
index ec96b1d6c3..a3997bc3b5 100644
--- a/crates/federate/src/inboxes.rs
+++ b/crates/federate/src/inboxes.rs
@@ -6,7 +6,7 @@ use lemmy_db_schema::{
   source::{activity::SentActivity, site::Site},
   utils::{ActualDbPool, DbPool},
 };
-use lemmy_db_views_actor::structs::CommunityFollowerView;
+use lemmy_db_views::structs::CommunityFollowerView;
 use lemmy_utils::error::LemmyResult;
 use reqwest::Url;
 use std::{
diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml
index 0edf436cdb..85a066964a 100644
--- a/crates/routes/Cargo.toml
+++ b/crates/routes/Cargo.toml
@@ -18,7 +18,6 @@ workspace = true
 [dependencies]
 lemmy_utils = { workspace = true, features = ["full"] }
 lemmy_db_views = { workspace = true }
-lemmy_db_views_actor = { workspace = true }
 lemmy_db_schema = { workspace = true }
 lemmy_api_common = { workspace = true, features = ["full"] }
 activitypub_federation = { workspace = true }
diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs
index 4368aa1720..401c29d893 100644
--- a/crates/routes/src/feeds.rs
+++ b/crates/routes/src/feeds.rs
@@ -13,10 +13,10 @@ use lemmy_db_schema::{
   PostSortType,
 };
 use lemmy_db_views::{
-  post_view::PostQuery,
-  structs::{PostView, SiteView},
+  combined::inbox_combined_view::InboxCombinedQuery,
+  post::post_view::PostQuery,
+  structs::{InboxCombinedView, PostView, SiteView},
 };
-use lemmy_db_views_actor::{inbox_combined_view::InboxCombinedQuery, structs::InboxCombinedView};
 use lemmy_utils::{
   cache_header::cache_1hour,
   error::{LemmyError, LemmyErrorType, LemmyResult},
diff --git a/crates/routes/src/images/download.rs b/crates/routes/src/images/download.rs
index 76f09a8d12..c4317c4dd1 100644
--- a/crates/routes/src/images/download.rs
+++ b/crates/routes/src/images/download.rs
@@ -125,5 +125,5 @@ pub(super) async fn do_get_image(
 pub(super) fn file_type(file_type: Option<String>, name: &str) -> String {
   file_type
     .clone()
-    .unwrap_or_else(|| name.split('.').last().unwrap_or("jpg").to_string())
+    .unwrap_or_else(|| name.split('.').next_back().unwrap_or("jpg").to_string())
 }
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index dc978244e1..ed4325deca 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -38,7 +38,7 @@ services:
     hostname: lemmy
     restart: unless-stopped
     environment:
-      - RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
+      - RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
       - RUST_BACKTRACE=full
     ports:
       # prometheus metrics can be enabled with the `prometheus` config option. they are available on
diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml
index bc4b5ea7f1..711e4eae0c 100644
--- a/docker/federation/docker-compose.yml
+++ b/docker/federation/docker-compose.yml
@@ -16,7 +16,7 @@ x-lemmy-default: &lemmy-default
     dockerfile: docker/Dockerfile
   environment:
     - RUST_BACKTRACE=1
-    - RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
+    - RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
   restart: always
 
 x-postgres-default: &postgres-default
diff --git a/migrations/2024-12-12-222846_add_search_combined_table/down.sql b/migrations/2024-12-12-222846_add_search_combined_table/down.sql
new file mode 100644
index 0000000000..477bb9b63e
--- /dev/null
+++ b/migrations/2024-12-12-222846_add_search_combined_table/down.sql
@@ -0,0 +1,5 @@
+ALTER TABLE person_aggregates
+    DROP COLUMN published;
+
+DROP TABLE search_combined;
+
diff --git a/migrations/2024-12-12-222846_add_search_combined_table/up.sql b/migrations/2024-12-12-222846_add_search_combined_table/up.sql
new file mode 100644
index 0000000000..f8edc7454a
--- /dev/null
+++ b/migrations/2024-12-12-222846_add_search_combined_table/up.sql
@@ -0,0 +1,80 @@
+-- Creates combined tables for
+-- Search: (post, comment, community, person)
+CREATE TABLE search_combined (
+    id serial PRIMARY KEY,
+    published timestamptz NOT NULL,
+    -- This is used for the top sort
+    -- For persons: its post score
+    -- For comments: score,
+    -- For posts: score,
+    -- For community: users active monthly
+    score bigint NOT NULL DEFAULT 0,
+    post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
+    comment_id int UNIQUE REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
+    community_id int UNIQUE REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE,
+    person_id int UNIQUE REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
+    -- Make sure only one of the columns is not null
+    CHECK (num_nonnulls (post_id, comment_id, community_id, person_id) = 1)
+);
+
+CREATE INDEX idx_search_combined_published ON search_combined (published DESC, id DESC);
+
+CREATE INDEX idx_search_combined_published_asc ON search_combined (reverse_timestamp_sort (published) DESC, id DESC);
+
+CREATE INDEX idx_search_combined_score ON search_combined (score DESC, id DESC);
+
+-- Add published to person_aggregates (it was missing for some reason)
+ALTER TABLE person_aggregates
+    ADD COLUMN published timestamptz NOT NULL DEFAULT now();
+
+UPDATE
+    person_aggregates pa
+SET
+    published = p.published
+FROM
+    person p
+WHERE
+    pa.person_id = p.id;
+
+-- Updating the history
+INSERT INTO search_combined (published, score, post_id, comment_id, community_id, person_id)
+SELECT
+    published,
+    score,
+    post_id,
+    NULL::int,
+    NULL::int,
+    NULL::int
+FROM
+    post_aggregates
+UNION ALL
+SELECT
+    published,
+    score,
+    NULL::int,
+    comment_id,
+    NULL::int,
+    NULL::int
+FROM
+    comment_aggregates
+UNION ALL
+SELECT
+    published,
+    users_active_month,
+    NULL::int,
+    NULL::int,
+    community_id,
+    NULL::int
+FROM
+    community_aggregates
+UNION ALL
+SELECT
+    published,
+    post_score,
+    NULL::int,
+    NULL::int,
+    NULL::int,
+    person_id
+FROM
+    person_aggregates;
+


Test patch:
diff --git a/api_tests/package.json b/api_tests/package.json
index f9c67eea77..9931367423 100644
--- a/api_tests/package.json
+++ b/api_tests/package.json
@@ -22,16 +22,16 @@
   },
   "devDependencies": {
     "@types/jest": "^29.5.12",
-    "@types/node": "^22.10.6",
-    "@typescript-eslint/eslint-plugin": "^8.20.0",
-    "@typescript-eslint/parser": "^8.20.0",
+    "@types/node": "^22.10.7",
+    "@typescript-eslint/eslint-plugin": "^8.21.0",
+    "@typescript-eslint/parser": "^8.21.0",
     "eslint": "^9.18.0",
-    "eslint-plugin-prettier": "^5.1.3",
+    "eslint-plugin-prettier": "^5.2.3",
     "jest": "^29.5.0",
-    "lemmy-js-client": "0.20.0-inbox-combined.1",
+    "lemmy-js-client": "0.20.0-search-combined.1",
     "prettier": "^3.4.2",
     "ts-jest": "^29.1.0",
     "typescript": "^5.7.3",
-    "typescript-eslint": "^8.20.0"
+    "typescript-eslint": "^8.21.0"
   }
 }
diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml
index 0a662e5767..a0721bba36 100644
--- a/api_tests/pnpm-lock.yaml
+++ b/api_tests/pnpm-lock.yaml
@@ -12,38 +12,38 @@ importers:
         specifier: ^29.5.12
         version: 29.5.14
       '@types/node':
-        specifier: ^22.10.6
-        version: 22.10.6
+        specifier: ^22.10.7
+        version: 22.10.7
       '@typescript-eslint/eslint-plugin':
-        specifier: ^8.20.0
-        version: 8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)
+        specifier: ^8.21.0
+        version: 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)
       '@typescript-eslint/parser':
-        specifier: ^8.20.0
-        version: 8.20.0(eslint@9.18.0)(typescript@5.7.3)
+        specifier: ^8.21.0
+        version: 8.21.0(eslint@9.18.0)(typescript@5.7.3)
       eslint:
         specifier: ^9.18.0
         version: 9.18.0
       eslint-plugin-prettier:
-        specifier: ^5.1.3
-        version: 5.2.1(eslint@9.18.0)(prettier@3.4.2)
+        specifier: ^5.2.3
+        version: 5.2.3(eslint@9.18.0)(prettier@3.4.2)
       jest:
         specifier: ^29.5.0
-        version: 29.7.0(@types/node@22.10.6)
+        version: 29.7.0(@types/node@22.10.7)
       lemmy-js-client:
-        specifier: 0.20.0-inbox-combined.1
-        version: 0.20.0-inbox-combined.1
+        specifier: 0.20.0-search-combined.1
+        version: 0.20.0-search-combined.1
       prettier:
         specifier: ^3.4.2
         version: 3.4.2
       ts-jest:
         specifier: ^29.1.0
-        version: 29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.10.6))(typescript@5.7.3)
+        version: 29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.10.7))(typescript@5.7.3)
       typescript:
         specifier: ^5.7.3
         version: 5.7.3
       typescript-eslint:
-        specifier: ^8.20.0
-        version: 8.20.0(eslint@9.18.0)(typescript@5.7.3)
+        specifier: ^8.21.0
+        version: 8.21.0(eslint@9.18.0)(typescript@5.7.3)
 
 packages:
 
@@ -422,8 +422,8 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/node@22.10.6':
-    resolution: {integrity: sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==}
+  '@types/node@22.10.7':
+    resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==}
 
   '@types/stack-utils@2.0.3':
     resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
@@ -434,51 +434,51 @@ packages:
   '@types/yargs@17.0.32':
     resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==}
 
-  '@typescript-eslint/eslint-plugin@8.20.0':
-    resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==}
+  '@typescript-eslint/eslint-plugin@8.21.0':
+    resolution: {integrity: sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <5.8.0'
 
-  '@typescript-eslint/parser@8.20.0':
-    resolution: {integrity: sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==}
+  '@typescript-eslint/parser@8.21.0':
+    resolution: {integrity: sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <5.8.0'
 
-  '@typescript-eslint/scope-manager@8.20.0':
-    resolution: {integrity: sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==}
+  '@typescript-eslint/scope-manager@8.21.0':
+    resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/type-utils@8.20.0':
-    resolution: {integrity: sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==}
+  '@typescript-eslint/type-utils@8.21.0':
+    resolution: {integrity: sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <5.8.0'
 
-  '@typescript-eslint/types@8.20.0':
-    resolution: {integrity: sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==}
+  '@typescript-eslint/types@8.21.0':
+    resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/typescript-estree@8.20.0':
-    resolution: {integrity: sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==}
+  '@typescript-eslint/typescript-estree@8.21.0':
+    resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '>=4.8.4 <5.8.0'
 
-  '@typescript-eslint/utils@8.20.0':
-    resolution: {integrity: sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==}
+  '@typescript-eslint/utils@8.21.0':
+    resolution: {integrity: sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
       typescript: '>=4.8.4 <5.8.0'
 
-  '@typescript-eslint/visitor-keys@8.20.0':
-    resolution: {integrity: sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==}
+  '@typescript-eslint/visitor-keys@8.21.0':
+    resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   acorn-jsx@5.3.2:
@@ -709,8 +709,8 @@ packages:
     resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
     engines: {node: '>=10'}
 
-  eslint-plugin-prettier@5.2.1:
-    resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==}
+  eslint-plugin-prettier@5.2.3:
+    resolution: {integrity: sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       '@types/eslint': '>=8.0.0'
@@ -1157,8 +1157,8 @@ packages:
     resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
     engines: {node: '>=6'}
 
-  lemmy-js-client@0.20.0-inbox-combined.1:
-    resolution: {integrity: sha512-sFJJePXdMHIVQwCa3fN+nIcIvfD7ZbBEZn08fmITXEA6/qbJLvZGWG/rEcRNkZM+lRKnhfrZihWKx1AHZE9wqA==}
+  lemmy-js-client@0.20.0-search-combined.1:
+    resolution: {integrity: sha512-K4bk6r+26lxZqL8WKiJlqfZb+ZnhNrZz1gAQTb4FQkGXQLFeqQ41ZyGmQZzjX+NPy7O9mt9d9d7zI8t/QYJ82Q==}
 
   leven@3.1.0:
     resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@@ -1528,8 +1528,8 @@ packages:
     resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
     engines: {node: '>=10'}
 
-  typescript-eslint@8.20.0:
-    resolution: {integrity: sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==}
+  typescript-eslint@8.21.0:
+    resolution: {integrity: sha512-txEKYY4XMKwPXxNkN8+AxAdX6iIJAPiJbHE/FpQccs/sxw8Lf26kqwC3cn0xkHlW8kEbLhkhCsjWuMveaY9Rxw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -1869,7 +1869,7 @@ snapshots:
   '@jest/console@29.7.0':
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
@@ -1882,14 +1882,14 @@ snapshots:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.9.0
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@22.10.6)
+      jest-config: 29.7.0(@types/node@22.10.7)
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -1914,7 +1914,7 @@ snapshots:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       jest-mock: 29.7.0
 
   '@jest/expect-utils@29.7.0':
@@ -1932,7 +1932,7 @@ snapshots:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -1954,7 +1954,7 @@ snapshots:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.22
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       collect-v8-coverage: 1.0.2
       exit: 0.1.2
@@ -2024,7 +2024,7 @@ snapshots:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       '@types/yargs': 17.0.32
       chalk: 4.1.2
 
@@ -2094,7 +2094,7 @@ snapshots:
 
   '@types/graceful-fs@4.1.9':
     dependencies:
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
 
   '@types/istanbul-lib-coverage@2.0.6': {}
 
@@ -2113,7 +2113,7 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/node@22.10.6':
+  '@types/node@22.10.7':
     dependencies:
       undici-types: 6.20.0
 
@@ -2125,14 +2125,14 @@ snapshots:
     dependencies:
       '@types/yargs-parser': 21.0.3
 
-  '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)':
+  '@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
-      '@typescript-eslint/parser': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
-      '@typescript-eslint/scope-manager': 8.20.0
-      '@typescript-eslint/type-utils': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
-      '@typescript-eslint/utils': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
-      '@typescript-eslint/visitor-keys': 8.20.0
+      '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/scope-manager': 8.21.0
+      '@typescript-eslint/type-utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/visitor-keys': 8.21.0
       eslint: 9.18.0
       graphemer: 1.4.0
       ignore: 5.3.2
@@ -2142,27 +2142,27 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.20.0(eslint@9.18.0)(typescript@5.7.3)':
+  '@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3)':
     dependencies:
-      '@typescript-eslint/scope-manager': 8.20.0
-      '@typescript-eslint/types': 8.20.0
-      '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3)
-      '@typescript-eslint/visitor-keys': 8.20.0
+      '@typescript-eslint/scope-manager': 8.21.0
+      '@typescript-eslint/types': 8.21.0
+      '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3)
+      '@typescript-eslint/visitor-keys': 8.21.0
       debug: 4.4.0
       eslint: 9.18.0
       typescript: 5.7.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@8.20.0':
+  '@typescript-eslint/scope-manager@8.21.0':
     dependencies:
-      '@typescript-eslint/types': 8.20.0
-      '@typescript-eslint/visitor-keys': 8.20.0
+      '@typescript-eslint/types': 8.21.0
+      '@typescript-eslint/visitor-keys': 8.21.0
 
-  '@typescript-eslint/type-utils@8.20.0(eslint@9.18.0)(typescript@5.7.3)':
+  '@typescript-eslint/type-utils@8.21.0(eslint@9.18.0)(typescript@5.7.3)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3)
-      '@typescript-eslint/utils': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3)
+      '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
       debug: 4.4.0
       eslint: 9.18.0
       ts-api-utils: 2.0.0(typescript@5.7.3)
@@ -2170,12 +2170,12 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@8.20.0': {}
+  '@typescript-eslint/types@8.21.0': {}
 
-  '@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)':
+  '@typescript-eslint/typescript-estree@8.21.0(typescript@5.7.3)':
     dependencies:
-      '@typescript-eslint/types': 8.20.0
-      '@typescript-eslint/visitor-keys': 8.20.0
+      '@typescript-eslint/types': 8.21.0
+      '@typescript-eslint/visitor-keys': 8.21.0
       debug: 4.4.0
       fast-glob: 3.3.3
       is-glob: 4.0.3
@@ -2186,20 +2186,20 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.20.0(eslint@9.18.0)(typescript@5.7.3)':
+  '@typescript-eslint/utils@8.21.0(eslint@9.18.0)(typescript@5.7.3)':
     dependencies:
       '@eslint-community/eslint-utils': 4.4.1(eslint@9.18.0)
-      '@typescript-eslint/scope-manager': 8.20.0
-      '@typescript-eslint/types': 8.20.0
-      '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3)
+      '@typescript-eslint/scope-manager': 8.21.0
+      '@typescript-eslint/types': 8.21.0
+      '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3)
       eslint: 9.18.0
       typescript: 5.7.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/visitor-keys@8.20.0':
+  '@typescript-eslint/visitor-keys@8.21.0':
     dependencies:
-      '@typescript-eslint/types': 8.20.0
+      '@typescript-eslint/types': 8.21.0
       eslint-visitor-keys: 4.2.0
 
   acorn-jsx@5.3.2(acorn@8.14.0):
@@ -2367,13 +2367,13 @@ snapshots:
 
   convert-source-map@2.0.0: {}
 
-  create-jest@29.7.0(@types/node@22.10.6):
+  create-jest@29.7.0(@types/node@22.10.7):
     dependencies:
       '@jest/types': 29.6.3
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@22.10.6)
+      jest-config: 29.7.0(@types/node@22.10.7)
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -2428,7 +2428,7 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
-  eslint-plugin-prettier@5.2.1(eslint@9.18.0)(prettier@3.4.2):
+  eslint-plugin-prettier@5.2.3(eslint@9.18.0)(prettier@3.4.2):
     dependencies:
       eslint: 9.18.0
       prettier: 3.4.2
@@ -2735,7 +2735,7 @@ snapshots:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.5.1
@@ -2755,16 +2755,16 @@ snapshots:
       - babel-plugin-macros
       - supports-color
 
-  jest-cli@29.7.0(@types/node@22.10.6):
+  jest-cli@29.7.0(@types/node@22.10.7):
     dependencies:
       '@jest/core': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@22.10.6)
+      create-jest: 29.7.0(@types/node@22.10.7)
       exit: 0.1.2
       import-local: 3.1.0
-      jest-config: 29.7.0(@types/node@22.10.6)
+      jest-config: 29.7.0(@types/node@22.10.7)
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.7.2
@@ -2774,7 +2774,7 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest-config@29.7.0(@types/node@22.10.6):
+  jest-config@29.7.0(@types/node@22.10.7):
     dependencies:
       '@babel/core': 7.23.9
       '@jest/test-sequencer': 29.7.0
@@ -2799,7 +2799,7 @@ snapshots:
       slash: 3.0.0
       strip-json-comments: 3.1.1
     optionalDependencies:
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
     transitivePeerDependencies:
       - babel-plugin-macros
       - supports-color
@@ -2828,7 +2828,7 @@ snapshots:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       jest-mock: 29.7.0
       jest-util: 29.7.0
 
@@ -2838,7 +2838,7 @@ snapshots:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.9
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -2877,7 +2877,7 @@ snapshots:
   jest-mock@29.7.0:
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       jest-util: 29.7.0
 
   jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -2912,7 +2912,7 @@ snapshots:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -2940,7 +2940,7 @@ snapshots:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       cjs-module-lexer: 1.2.3
       collect-v8-coverage: 1.0.2
@@ -2986,7 +2986,7 @@ snapshots:
   jest-util@29.7.0:
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       chalk: 4.1.2
       ci-info: 3.9.0
       graceful-fs: 4.2.11
@@ -3005,7 +3005,7 @@ snapshots:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -3014,17 +3014,17 @@ snapshots:
 
   jest-worker@29.7.0:
     dependencies:
-      '@types/node': 22.10.6
+      '@types/node': 22.10.7
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
 
-  jest@29.7.0(@types/node@22.10.6):
+  jest@29.7.0(@types/node@22.10.7):
     dependencies:
       '@jest/core': 29.7.0
       '@jest/types': 29.6.3
       import-local: 3.1.0
-      jest-cli: 29.7.0(@types/node@22.10.6)
+      jest-cli: 29.7.0(@types/node@22.10.7)
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -3060,7 +3060,7 @@ snapshots:
 
   kleur@3.0.3: {}
 
-  lemmy-js-client@0.20.0-inbox-combined.1: {}
+  lemmy-js-client@0.20.0-search-combined.1: {}
 
   leven@3.1.0: {}
 
@@ -3339,12 +3339,12 @@ snapshots:
     dependencies:
       typescript: 5.7.3
 
-  ts-jest@29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.10.6))(typescript@5.7.3):
+  ts-jest@29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.10.7))(typescript@5.7.3):
     dependencies:
       bs-logger: 0.2.6
       ejs: 3.1.10
       fast-json-stable-stringify: 2.1.0
-      jest: 29.7.0(@types/node@22.10.6)
+      jest: 29.7.0(@types/node@22.10.7)
       jest-util: 29.7.0
       json5: 2.2.3
       lodash.memoize: 4.1.2
@@ -3368,11 +3368,11 @@ snapshots:
 
   type-fest@0.21.3: {}
 
-  typescript-eslint@8.20.0(eslint@9.18.0)(typescript@5.7.3):
+  typescript-eslint@8.21.0(eslint@9.18.0)(typescript@5.7.3):
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)
-      '@typescript-eslint/parser': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
-      '@typescript-eslint/utils': 8.20.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/eslint-plugin': 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/parser': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
+      '@typescript-eslint/utils': 8.21.0(eslint@9.18.0)(typescript@5.7.3)
       eslint: 9.18.0
       typescript: 5.7.3
     transitivePeerDependencies:
diff --git a/api_tests/prepare-drone-federation-test.sh b/api_tests/prepare-drone-federation-test.sh
index f05b4328ef..cb4fcf880d 100755
--- a/api_tests/prepare-drone-federation-test.sh
+++ b/api_tests/prepare-drone-federation-test.sh
@@ -9,7 +9,7 @@ then
 fi
 
 export RUST_BACKTRACE=1
-export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
+export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
 
 export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
 
diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts
index 62c8e7ac89..2258b020ed 100644
--- a/api_tests/src/community.spec.ts
+++ b/api_tests/src/community.spec.ts
@@ -488,7 +488,7 @@ test("Dont receive community activities after unsubscribe", async () => {
   // await longDelay();
 
   let postResBeta = searchPostLocal(beta, postRes.post_view.post);
-  expect((await postResBeta).posts.length).toBe(0);
+  expect((await postResBeta).results.length).toBe(0);
 });
 
 test("Fetch community, includes posts", async () => {
diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts
index 5e9513df8c..88dca842e3 100644
--- a/api_tests/src/shared.ts
+++ b/api_tests/src/shared.ts
@@ -324,9 +324,8 @@ export async function searchPostLocal(
   post: Post,
 ): Promise<SearchResponse> {
   let form: Search = {
-    q: post.name,
+    search_term: post.name,
     type_: "Posts",
-    sort: "TopAll",
     listing_type: "All",
   };
   return api.search(form);
@@ -339,7 +338,7 @@ export async function waitForPost(
   checker: (t: PostView | undefined) => boolean = p => !!p,
 ) {
   return waitUntil<PostView>(
-    () => searchPostLocal(api, post).then(p => p.posts[0]),
+    () => searchPostLocal(api, post).then(p => p.results[0] as PostView),
     checker,
   );
 }