Add delete-empty-profiles CLI command

This commit is contained in:
silverpill 2022-11-17 21:40:07 +00:00
parent ec03476b58
commit 8045b9f543
3 changed files with 100 additions and 0 deletions

View file

@ -32,6 +32,7 @@ async fn main() {
SubCommand::DeleteExtraneousPosts(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::DeleteExtraneousPosts(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::DeleteUnusedAttachments(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::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::UpdateCurrentBlock(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::ResetSubscriptions(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(), SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),

View file

@ -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::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id};
use crate::models::profiles::queries::{ use crate::models::profiles::queries::{
delete_profile, delete_profile,
find_empty_profiles,
get_profile_by_id, get_profile_by_id,
get_profile_by_remote_actor_id, get_profile_by_remote_actor_id,
}; };
@ -59,6 +60,7 @@ pub enum SubCommand {
DeleteExtraneousPosts(DeleteExtraneousPosts), DeleteExtraneousPosts(DeleteExtraneousPosts),
DeleteUnusedAttachments(DeleteUnusedAttachments), DeleteUnusedAttachments(DeleteUnusedAttachments),
DeleteOrphanedFiles(DeleteOrphanedFiles), DeleteOrphanedFiles(DeleteOrphanedFiles),
DeleteEmptyProfiles(DeleteEmptyProfiles),
UpdateCurrentBlock(UpdateCurrentBlock), UpdateCurrentBlock(UpdateCurrentBlock),
ResetSubscriptions(ResetSubscriptions), ResetSubscriptions(ResetSubscriptions),
CreateMoneroWallet(CreateMoneroWallet), 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 /// Update blockchain synchronization starting block
#[derive(Parser)] #[derive(Parser)]
pub struct UpdateCurrentBlock { pub struct UpdateCurrentBlock {

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
use uuid::Uuid; use uuid::Uuid;
@ -573,6 +574,69 @@ pub async fn update_post_count(
Ok(profile) 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<Utc>,
) -> Result<Vec<Uuid>, 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<Uuid> = rows.iter()
.map(|row| row.try_get("id"))
.collect::<Result<_, _>>()?;
Ok(ids)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serial_test::serial; use serial_test::serial;
@ -744,4 +808,13 @@ mod tests {
assert_eq!(profiles.len(), 1); assert_eq!(profiles.len(), 1);
assert_eq!(profiles[0].id, profile.id); 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);
}
} }