Repository: LemmyNet/lemmy (language: rust)

Issue:
View modlog for all moderated communities
### Requirements

- [X] Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support
- [X] Did you check to see if this issue already exists?
- [X] Is this only a feature request? Do not put multiple feature requests in one issue.
- [X] Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.

### Is your proposal related to a problem?

The new v0.19 Moderator View is great. Similar to this, I would like to be able to view all mod log items for my moderated communities.

### Describe the solution you'd like.

Ability to get modlog filtered by `ListingType`

### Describe alternatives you've considered.

No alternative exists.

### Additional context

_No response_


Discussion / hints:

Fix patch:
diff --git a/crates/api/src/site/mod_log.rs b/crates/api/src/site/mod_log.rs
index 2e6c9b42dd..90d74727f7 100644
--- a/crates/api/src/site/mod_log.rs
+++ b/crates/api/src/site/mod_log.rs
@@ -18,10 +18,11 @@ pub async fn get_mod_log(
   check_private_instance(&local_user_view, &local_site)?;
 
   let type_ = data.type_;
+  let listing_type = data.listing_type;
   let community_id = data.community_id;
 
-  let is_mod_or_admin = if let Some(local_user_view) = local_user_view {
-    check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool())
+  let is_mod_or_admin = if let Some(local_user_view) = &local_user_view {
+    check_community_mod_of_any_or_admin_action(local_user_view, &mut context.pool())
       .await
       .is_ok()
   } else {
@@ -37,6 +38,7 @@ pub async fn get_mod_log(
   let other_person_id = data.other_person_id;
   let post_id = data.post_id;
   let comment_id = data.comment_id;
+  let local_user = local_user_view.as_ref().map(|u| &u.local_user);
 
   // parse pagination token
   let page_after = if let Some(pa) = &data.page_cursor {
@@ -48,9 +50,11 @@ pub async fn get_mod_log(
 
   let modlog = ModlogCombinedQuery {
     type_,
+    listing_type,
     community_id,
     mod_person_id,
     other_person_id,
+    local_user,
     post_id,
     comment_id,
     hide_modlog_names: Some(hide_modlog_names),
diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs
index 25dcf90b3e..474d586e81 100644
--- a/crates/api_common/src/site.rs
+++ b/crates/api_common/src/site.rs
@@ -124,16 +124,26 @@ pub struct ResolveObjectResponse {
 #[cfg_attr(feature = "full", ts(export))]
 /// Fetches the modlog.
 pub struct GetModlog {
+  /// Filter by the moderator.
   #[cfg_attr(feature = "full", ts(optional))]
   pub mod_person_id: Option<PersonId>,
+  /// Filter by the community.
   #[cfg_attr(feature = "full", ts(optional))]
   pub community_id: Option<CommunityId>,
+  /// Filter by the modlog action type.
   #[cfg_attr(feature = "full", ts(optional))]
   pub type_: Option<ModlogActionType>,
+  /// Filter by listing type. When not using All, it will remove the non-community modlog entries,
+  /// such as site bans, instance blocks, adding an admin, etc.
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub listing_type: Option<ListingType>,
+  /// Filter by the other / modded person.
   #[cfg_attr(feature = "full", ts(optional))]
   pub other_person_id: Option<PersonId>,
+  /// Filter by post. Will include comments of that post.
   #[cfg_attr(feature = "full", ts(optional))]
   pub post_id: Option<PostId>,
+  /// Filter by comment.
   #[cfg_attr(feature = "full", ts(optional))]
   pub comment_id: Option<CommentId>,
   #[cfg_attr(feature = "full", ts(optional))]
diff --git a/crates/db_views/src/combined/modlog_combined_view.rs b/crates/db_views/src/combined/modlog_combined_view.rs
index af2b655cc6..9e5279be00 100644
--- a/crates/db_views/src/combined/modlog_combined_view.rs
+++ b/crates/db_views/src/combined/modlog_combined_view.rs
@@ -34,6 +34,7 @@ use diesel_async::RunQueryDsl;
 use i_love_jesus::PaginatedQueryBuilder;
 use lemmy_db_schema::{
   aliases,
+  impls::local_user::LocalUserOptionHelper,
   newtypes::{CommentId, CommunityId, PersonId, PostId},
   schema::{
     admin_allow_instance,
@@ -44,6 +45,7 @@ use lemmy_db_schema::{
     admin_purge_post,
     comment,
     community,
+    community_actions,
     instance,
     mod_add,
     mod_add_community,
@@ -60,15 +62,23 @@ use lemmy_db_schema::{
     person,
     post,
   },
-  source::combined::modlog::{modlog_combined_keys as key, ModlogCombined},
+  source::{
+    combined::modlog::{modlog_combined_keys as key, ModlogCombined},
+    local_user::LocalUser,
+  },
   traits::InternalToCombinedView,
   utils::{get_conn, DbPool},
+  ListingType,
   ModlogActionType,
 };
 use lemmy_utils::error::LemmyResult;
 impl ModlogCombinedViewInternal {
   #[diesel::dsl::auto_type(no_type_alias)]
-  fn joins(mod_person_id: Option<PersonId>, hide_modlog_names: Option<bool>) -> _ {
+  fn joins(
+    mod_person_id: Option<PersonId>,
+    hide_modlog_names: Option<bool>,
+    my_person_id: Option<PersonId>,
+  ) -> _ {
     // The modded / other person
     let other_person = aliases::person1.field(person::id);
 
@@ -78,101 +88,117 @@ impl ModlogCombinedViewInternal {
     // The query for the admin / mod person
     // It needs an OR condition to every mod table
     // After this you can use person::id to refer to the moderator
-    let moderator_names_join = show_mod_names_expr
-      .or(person::id.nullable().eq(mod_person_id))
-      .and(
-        admin_allow_instance::admin_person_id
-          .eq(person::id)
-          .or(admin_block_instance::admin_person_id.eq(person::id))
-          .or(admin_purge_comment::admin_person_id.eq(person::id))
-          .or(admin_purge_community::admin_person_id.eq(person::id))
-          .or(admin_purge_person::admin_person_id.eq(person::id))
-          .or(admin_purge_post::admin_person_id.eq(person::id))
-          .or(mod_add::mod_person_id.eq(person::id))
-          .or(mod_add_community::mod_person_id.eq(person::id))
-          .or(mod_ban::mod_person_id.eq(person::id))
-          .or(mod_ban_from_community::mod_person_id.eq(person::id))
-          .or(mod_feature_post::mod_person_id.eq(person::id))
-          .or(mod_hide_community::mod_person_id.eq(person::id))
-          .or(mod_lock_post::mod_person_id.eq(person::id))
-          .or(mod_remove_comment::mod_person_id.eq(person::id))
-          .or(mod_remove_community::mod_person_id.eq(person::id))
-          .or(mod_remove_post::mod_person_id.eq(person::id))
-          .or(mod_transfer_community::mod_person_id.eq(person::id)),
-      );
+    let moderator_names_join = person::table.on(
+      show_mod_names_expr
+        .or(person::id.nullable().eq(mod_person_id))
+        .and(
+          admin_allow_instance::admin_person_id
+            .eq(person::id)
+            .or(admin_block_instance::admin_person_id.eq(person::id))
+            .or(admin_purge_comment::admin_person_id.eq(person::id))
+            .or(admin_purge_community::admin_person_id.eq(person::id))
+            .or(admin_purge_person::admin_person_id.eq(person::id))
+            .or(admin_purge_post::admin_person_id.eq(person::id))
+            .or(mod_add::mod_person_id.eq(person::id))
+            .or(mod_add_community::mod_person_id.eq(person::id))
+            .or(mod_ban::mod_person_id.eq(person::id))
+            .or(mod_ban_from_community::mod_person_id.eq(person::id))
+            .or(mod_feature_post::mod_person_id.eq(person::id))
+            .or(mod_hide_community::mod_person_id.eq(person::id))
+            .or(mod_lock_post::mod_person_id.eq(person::id))
+            .or(mod_remove_comment::mod_person_id.eq(person::id))
+            .or(mod_remove_community::mod_person_id.eq(person::id))
+            .or(mod_remove_post::mod_person_id.eq(person::id))
+            .or(mod_transfer_community::mod_person_id.eq(person::id)),
+        ),
+    );
+
+    let other_person_join = aliases::person1.on(
+      mod_add::other_person_id
+        .eq(other_person)
+        .or(mod_add_community::other_person_id.eq(other_person))
+        .or(mod_ban::other_person_id.eq(other_person))
+        .or(mod_ban_from_community::other_person_id.eq(other_person))
+        // Some tables don't have the other_person_id directly, so you need to join
+        .or(
+          mod_feature_post::id
+            .is_not_null()
+            .and(post::creator_id.eq(other_person)),
+        )
+        .or(
+          mod_lock_post::id
+            .is_not_null()
+            .and(post::creator_id.eq(other_person)),
+        )
+        .or(
+          mod_remove_comment::id
+            .is_not_null()
+            .and(comment::creator_id.eq(other_person)),
+        )
+        .or(
+          mod_remove_post::id
+            .is_not_null()
+            .and(post::creator_id.eq(other_person)),
+        )
+        .or(mod_transfer_community::other_person_id.eq(other_person)),
+    );
+
+    let comment_join = comment::table.on(mod_remove_comment::comment_id.eq(comment::id));
+
+    let post_join = post::table.on(
+      admin_purge_comment::post_id
+        .eq(post::id)
+        .or(mod_feature_post::post_id.eq(post::id))
+        .or(mod_lock_post::post_id.eq(post::id))
+        .or(
+          mod_remove_comment::id
+            .is_not_null()
+            .and(comment::post_id.eq(post::id)),
+        )
+        .or(mod_remove_post::post_id.eq(post::id)),
+    );
 
-    let other_person_join = mod_add::other_person_id
-      .eq(other_person)
-      .or(mod_add_community::other_person_id.eq(other_person))
-      .or(mod_ban::other_person_id.eq(other_person))
-      .or(mod_ban_from_community::other_person_id.eq(other_person))
-      // Some tables don't have the other_person_id directly, so you need to join
-      .or(
-        mod_feature_post::id
-          .is_not_null()
-          .and(post::creator_id.eq(other_person)),
-      )
-      .or(
-        mod_lock_post::id
-          .is_not_null()
-          .and(post::creator_id.eq(other_person)),
-      )
-      .or(
-        mod_remove_comment::id
-          .is_not_null()
-          .and(comment::creator_id.eq(other_person)),
-      )
-      .or(
-        mod_remove_post::id
-          .is_not_null()
-          .and(post::creator_id.eq(other_person)),
-      )
-      .or(mod_transfer_community::other_person_id.eq(other_person));
-
-    let comment_join = mod_remove_comment::comment_id.eq(comment::id);
-
-    let post_join = admin_purge_comment::post_id
-      .eq(post::id)
-      .or(mod_feature_post::post_id.eq(post::id))
-      .or(mod_lock_post::post_id.eq(post::id))
-      .or(
-        mod_remove_comment::id
-          .is_not_null()
-          .and(comment::post_id.eq(post::id)),
-      )
-      .or(mod_remove_post::post_id.eq(post::id));
-
-    let community_join = admin_purge_post::community_id
-      .eq(community::id)
-      .or(mod_add_community::community_id.eq(community::id))
-      .or(mod_ban_from_community::community_id.eq(community::id))
-      .or(
-        mod_feature_post::id
-          .is_not_null()
-          .and(post::community_id.eq(community::id)),
-      )
-      .or(mod_hide_community::community_id.eq(community::id))
-      .or(
-        mod_lock_post::id
-          .is_not_null()
-          .and(post::community_id.eq(community::id)),
-      )
-      .or(
-        mod_remove_comment::id
-          .is_not_null()
-          .and(post::community_id.eq(community::id)),
-      )
-      .or(mod_remove_community::community_id.eq(community::id))
-      .or(
-        mod_remove_post::id
-          .is_not_null()
-          .and(post::community_id.eq(community::id)),
-      )
-      .or(mod_transfer_community::community_id.eq(community::id));
-
-    let instance_join = admin_allow_instance::instance_id
-      .eq(instance::id)
-      .or(admin_block_instance::instance_id.eq(instance::id));
+    let community_join = community::table.on(
+      admin_purge_post::community_id
+        .eq(community::id)
+        .or(mod_add_community::community_id.eq(community::id))
+        .or(mod_ban_from_community::community_id.eq(community::id))
+        .or(
+          mod_feature_post::id
+            .is_not_null()
+            .and(post::community_id.eq(community::id)),
+        )
+        .or(mod_hide_community::community_id.eq(community::id))
+        .or(
+          mod_lock_post::id
+            .is_not_null()
+            .and(post::community_id.eq(community::id)),
+        )
+        .or(
+          mod_remove_comment::id
+            .is_not_null()
+            .and(post::community_id.eq(community::id)),
+        )
+        .or(mod_remove_community::community_id.eq(community::id))
+        .or(
+          mod_remove_post::id
+            .is_not_null()
+            .and(post::community_id.eq(community::id)),
+        )
+        .or(mod_transfer_community::community_id.eq(community::id)),
+    );
+
+    let instance_join = instance::table.on(
+      admin_allow_instance::instance_id
+        .eq(instance::id)
+        .or(admin_block_instance::instance_id.eq(instance::id)),
+    );
+
+    let community_actions_join = community_actions::table.on(
+      community_actions::community_id
+        .eq(community::id)
+        .and(community_actions::person_id.nullable().eq(my_person_id)),
+    );
 
     modlog_combined::table
       .left_join(admin_allow_instance::table)
@@ -192,18 +218,13 @@ impl ModlogCombinedViewInternal {
       .left_join(mod_remove_community::table)
       .left_join(mod_remove_post::table)
       .left_join(mod_transfer_community::table)
-      // The moderator
-      .left_join(person::table.on(moderator_names_join))
-      // The comment
-      .left_join(comment::table.on(comment_join))
-      // The post
-      .left_join(post::table.on(post_join))
-      // The community
-      .left_join(community::table.on(community_join))
-      // The instance
-      .left_join(instance::table.on(instance_join))
-      // The other / modded person
-      .left_join(aliases::person1.on(other_person_join))
+      .left_join(moderator_names_join)
+      .left_join(comment_join)
+      .left_join(post_join)
+      .left_join(community_join)
+      .left_join(instance_join)
+      .left_join(other_person_join)
+      .left_join(community_actions_join)
   }
 }
 
@@ -284,26 +305,30 @@ pub struct PaginationCursorData(ModlogCombined);
 
 #[derive(Default)]
 /// Querying / filtering the modlog.
-pub struct ModlogCombinedQuery {
+pub struct ModlogCombinedQuery<'a> {
   pub type_: Option<ModlogActionType>,
+  pub listing_type: Option<ListingType>,
   pub comment_id: Option<CommentId>,
   pub post_id: Option<PostId>,
   pub community_id: Option<CommunityId>,
   pub hide_modlog_names: Option<bool>,
+  pub local_user: Option<&'a LocalUser>,
   pub mod_person_id: Option<PersonId>,
   pub other_person_id: Option<PersonId>,
   pub page_after: Option<PaginationCursorData>,
   pub page_back: Option<bool>,
 }
 
-impl ModlogCombinedQuery {
+impl ModlogCombinedQuery<'_> {
   pub async fn list(self, pool: &mut DbPool<'_>) -> LemmyResult<Vec<ModlogCombinedView>> {
     let conn = &mut get_conn(pool).await?;
     let other_person = aliases::person1.field(person::id);
+    let my_person_id = self.local_user.person_id();
 
-    let mut query = ModlogCombinedViewInternal::joins(self.mod_person_id, self.hide_modlog_names)
-      .select(ModlogCombinedViewInternal::as_select())
-      .into_boxed();
+    let mut query =
+      ModlogCombinedViewInternal::joins(self.mod_person_id, self.hide_modlog_names, my_person_id)
+        .select(ModlogCombinedViewInternal::as_select())
+        .into_boxed();
 
     if let Some(mod_person_id) = self.mod_person_id {
       query = query.filter(person::id.eq(mod_person_id));
@@ -355,6 +380,16 @@ impl ModlogCombinedQuery {
       }
     }
 
+    let is_subscribed = community_actions::followed.is_not_null();
+    query = match self.listing_type.unwrap_or(ListingType::All) {
+      ListingType::All => query,
+      ListingType::Subscribed => query.filter(is_subscribed),
+      ListingType::Local => query
+        .filter(community::local.eq(true))
+        .filter(community::hidden.eq(false).or(is_subscribed)),
+      ListingType::ModeratorView => 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);


Test patch: