diff --git a/FEDERATION.md b/FEDERATION.md index ea7484b..6c38131 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -15,6 +15,7 @@ The following activities are supported: - Undo(Announce) - Follow(Person) - Update(Person) +- Delete(Person) And these additional standards: diff --git a/src/activitypub/builders/delete_person.rs b/src/activitypub/builders/delete_person.rs new file mode 100644 index 0000000..d3592e8 --- /dev/null +++ b/src/activitypub/builders/delete_person.rs @@ -0,0 +1,91 @@ +use tokio_postgres::GenericClient; +use uuid::Uuid; + +use crate::activitypub::{ + activity::{create_activity, Activity}, + actor::Actor, + constants::AP_PUBLIC, + deliverer::OutgoingActivity, + vocabulary::DELETE, +}; +use crate::config::Instance; +use crate::errors::DatabaseError; +use crate::models::relationships::queries::{get_followers, get_following}; +use crate::models::users::types::User; + +fn build_delete_person( + instance_url: &str, + user: &User, +) -> Activity { + let actor_id = user.profile.actor_id(instance_url); + let activity_id = format!("{}/delete", actor_id); + create_activity( + instance_url, + &user.profile.username, + DELETE, + activity_id, + actor_id, + vec![AP_PUBLIC.to_string()], + vec![], + ) +} + +async fn get_delete_person_recipients( + db_client: &impl GenericClient, + user_id: &Uuid, +) -> Result, DatabaseError> { + let followers = get_followers(db_client, user_id, None, None).await?; + let following = get_following(db_client, user_id, None, None).await?; + let mut recipients = vec![]; + for profile in followers.into_iter().chain(following.into_iter()) { + if let Some(remote_actor) = profile.actor_json { + recipients.push(remote_actor); + }; + }; + Ok(recipients) +} + +pub async fn prepare_delete_person( + db_client: &impl GenericClient, + instance: Instance, + user: &User, +) -> Result { + let activity = build_delete_person(&instance.url(), user); + let recipients = get_delete_person_recipients(db_client, &user.id).await?; + Ok(OutgoingActivity { + instance, + sender: user.clone(), + activity, + recipients, + }) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use crate::models::profiles::types::DbActorProfile; + use super::*; + + const INSTANCE_URL: &str = "https://example.com"; + + #[test] + fn test_build_delete_person() { + let user = User { + profile: DbActorProfile { + username: "testuser".to_string(), + ..Default::default() + }, + ..Default::default() + }; + let activity = build_delete_person(INSTANCE_URL, &user); + assert_eq!( + activity.id, + format!("{}/users/testuser/delete", INSTANCE_URL), + ); + assert_eq!( + activity.object, + format!("{}/users/testuser", INSTANCE_URL), + ); + assert_eq!(activity.to.unwrap(), json!([AP_PUBLIC])); + } +} diff --git a/src/activitypub/builders/mod.rs b/src/activitypub/builders/mod.rs index e7c2f99..b440779 100644 --- a/src/activitypub/builders/mod.rs +++ b/src/activitypub/builders/mod.rs @@ -1,3 +1,4 @@ pub mod delete_note; +pub mod delete_person; pub mod undo_follow; pub mod update_person; diff --git a/src/bin/mitractl.rs b/src/bin/mitractl.rs index 7fa3a45..f01f6cf 100644 --- a/src/bin/mitractl.rs +++ b/src/bin/mitractl.rs @@ -3,6 +3,7 @@ use clap::Parser; use uuid::Uuid; use mitra::activitypub::builders::delete_note::prepare_delete_note; +use mitra::activitypub::builders::delete_person::prepare_delete_person; use mitra::activitypub::fetcher::fetchers::fetch_actor; use mitra::activitypub::handlers::update_person::update_actor; use mitra::config; @@ -13,7 +14,7 @@ use mitra::ethereum::utils::key_to_ethereum_address; use mitra::logger::configure_logger; use mitra::models::attachments::queries::delete_unused_attachments; use mitra::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id}; -use mitra::models::profiles::queries::delete_profile; +use mitra::models::profiles::queries::{delete_profile, get_profile_by_id}; use mitra::models::users::queries::{ create_invite_code, get_invite_codes, @@ -149,8 +150,20 @@ async fn main() { println!("profile updated"); }, SubCommand::DeleteProfile(subopts) => { - let deletion_queue = delete_profile(db_client, &subopts.id).await.unwrap(); + let profile = get_profile_by_id(db_client, &subopts.id).await.unwrap(); + let mut maybe_delete_person = None; + if profile.is_local() { + let user = get_user_by_id(db_client, &profile.id).await.unwrap(); + let activity = prepare_delete_person(db_client, config.instance(), &user) + .await.unwrap(); + maybe_delete_person = Some(activity); + }; + let deletion_queue = delete_profile(db_client, &profile.id).await.unwrap(); deletion_queue.process(&config).await; + // Send Delete(Person) activities + if let Some(activity) = maybe_delete_person { + activity.deliver().await.unwrap(); + }; println!("profile deleted"); }, SubCommand::DeletePost(subopts) => {