From 8045b9f543186e67c27eda66f05423b1dad6300f Mon Sep 17 00:00:00 2001 From: silverpill Date: Thu, 17 Nov 2022 21:40:07 +0000 Subject: [PATCH] Add delete-empty-profiles CLI command --- src/bin/mitractl.rs | 1 + src/cli.rs | 26 ++++++++++++ src/models/profiles/queries.rs | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/bin/mitractl.rs b/src/bin/mitractl.rs index 8fddfd0..9d7d800 100644 --- a/src/bin/mitractl.rs +++ b/src/bin/mitractl.rs @@ -32,6 +32,7 @@ async fn main() { SubCommand::DeleteExtraneousPosts(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::DeleteUnusedAttachments(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::DeleteOrphanedFiles(cmd) => cmd.execute(&config, db_client).await.unwrap(), + SubCommand::DeleteEmptyProfiles(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::UpdateCurrentBlock(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(), diff --git a/src/cli.rs b/src/cli.rs index f0bb237..9971c59 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,6 +17,7 @@ use crate::models::cleanup::find_orphaned_files; use crate::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id}; use crate::models::profiles::queries::{ delete_profile, + find_empty_profiles, get_profile_by_id, get_profile_by_remote_actor_id, }; @@ -59,6 +60,7 @@ pub enum SubCommand { DeleteExtraneousPosts(DeleteExtraneousPosts), DeleteUnusedAttachments(DeleteUnusedAttachments), DeleteOrphanedFiles(DeleteOrphanedFiles), + DeleteEmptyProfiles(DeleteEmptyProfiles), UpdateCurrentBlock(UpdateCurrentBlock), ResetSubscriptions(ResetSubscriptions), CreateMoneroWallet(CreateMoneroWallet), @@ -316,6 +318,30 @@ impl DeleteOrphanedFiles { } } +/// Delete empty remote profiles +#[derive(Parser)] +pub struct DeleteEmptyProfiles { + days: i64, +} + +impl DeleteEmptyProfiles { + pub async fn execute( + &self, + config: &Config, + db_client: &mut impl GenericClient, + ) -> Result<(), Error> { + let updated_before = Utc::now() - Duration::days(self.days); + let profiles = find_empty_profiles(db_client, &updated_before).await?; + for profile_id in profiles { + let profile = get_profile_by_id(db_client, &profile_id).await?; + let deletion_queue = delete_profile(db_client, &profile.id).await?; + deletion_queue.process(config).await; + println!("profile {} deleted", profile.acct); + }; + Ok(()) + } +} + /// Update blockchain synchronization starting block #[derive(Parser)] pub struct UpdateCurrentBlock { diff --git a/src/models/profiles/queries.rs b/src/models/profiles/queries.rs index 2486d7b..02d405d 100644 --- a/src/models/profiles/queries.rs +++ b/src/models/profiles/queries.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use tokio_postgres::GenericClient; use uuid::Uuid; @@ -573,6 +574,69 @@ pub async fn update_post_count( Ok(profile) } +/// Finds all empty remote profiles +/// (without any posts, reactions, relationships) +/// updated before the specified date +pub async fn find_empty_profiles( + db_client: &impl GenericClient, + updated_before: &DateTime, +) -> Result, DatabaseError> { + let rows = db_client.query( + " + SELECT actor_profile.id + FROM actor_profile + WHERE + actor_profile.hostname IS NOT NULL + AND actor_profile.updated_at < $1 + AND NOT EXISTS ( + SELECT 1 FROM relationship + WHERE + source_id = actor_profile.id + OR target_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM follow_request + WHERE + source_id = actor_profile.id + OR target_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM post + WHERE author_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM post_reaction + WHERE author_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM media_attachment + WHERE owner_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM mention + WHERE profile_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM notification + WHERE sender_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM invoice + WHERE sender_id = actor_profile.id + ) + AND NOT EXISTS ( + SELECT 1 FROM subscription + WHERE sender_id = actor_profile.id + ) + ", + &[&updated_before], + ).await?; + let ids: Vec = rows.iter() + .map(|row| row.try_get("id")) + .collect::>()?; + Ok(ids) +} + #[cfg(test)] mod tests { use serial_test::serial; @@ -744,4 +808,13 @@ mod tests { assert_eq!(profiles.len(), 1); assert_eq!(profiles[0].id, profile.id); } + + #[tokio::test] + #[serial] + async fn test_find_empty_profiles() { + let db_client = &mut create_test_database().await; + let updated_before = Utc::now(); + let profiles = find_empty_profiles(db_client, &updated_before).await.unwrap(); + assert_eq!(profiles.is_empty(), true); + } }