diff --git a/.woodpecker.yml b/.woodpecker.yml index 0c7efbd8535bf0c9de0f14d431978110242c1ec4..88bf3be0cfe4ca01140e69b27f11a9cf89b784bd 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -10,9 +10,9 @@ variables: - "**/Cargo.toml" - "Cargo.lock" # database migrations - - "migrations" + - "migrations/**" # typescript tests - - "api_tests" + - "api_tests/**" # config files and scripts used by ci - ".woodpecker.yml" - ".rustfmt.toml" diff --git a/api_tests/package.json b/api_tests/package.json index 1810b8d2ffa5449ac05acf151768513c903f7d8c..61c11d00a33d7b34d046fc590475f1de89f5bba1 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -19,7 +19,7 @@ "eslint": "^8.40.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^29.5.0", - "lemmy-js-client": "0.18.3-rc.3", + "lemmy-js-client": "0.19.0-rc.2", "prettier": "^3.0.0", "ts-jest": "^29.1.0", "typescript": "^5.0.4" diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 6110f1e341c28953dfb9481cfd7cb68f5949c67d..bc760fd336a30e3be0cb86ad748deecc47de4e7e 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -707,6 +707,7 @@ export async function getPersonDetails( export async function deleteUser(api: API): Promise<DeleteAccountResponse> { let form: DeleteAccount = { auth: api.auth, + delete_content: true, password, }; return api.client.deleteAccount(form); diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 275dcd067fde747e3204d0687ae0a4d38869b974..b243c1b6591e1d74704f79f716e22e5c08268927 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2174,10 +2174,10 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lemmy-js-client@0.18.3-rc.3: - version "0.18.3-rc.3" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.3-rc.3.tgz#fc6489eb141bd09558bca38d9e46b40771a29f37" - integrity sha512-njixgXk4uMU4gGifnljwhSe9Kf445C4wAXcXhtpTtwPPLXpHQgxA1RASMb9Uq4zblfE6nC2JbrAka8y8N2N/Bw== +lemmy-js-client@0.19.0-rc.2: + version "0.19.0-rc.2" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.2.tgz#c3cb511b27f92538909a2b91a0f8527b1abad958" + integrity sha512-FXuf8s7bpBVkHL/OGWDb/0aGIrJ7uv3d4Xt1h6zmNDhw6MmmuD8RXgCHiS2jqhxjAEp96Dpl1NFXbpmKpix7tQ== dependencies: cross-fetch "^3.1.5" form-data "^4.0.0" diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 490a514488403e3f3320e856e1c31ea671c6d64d..3a3a240da5323605107f2fddfe47d0ca8c5700d2 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -47,13 +47,7 @@ pub async fn ban_from_site( // Remove their data if that's desired let remove_data = data.remove_data.unwrap_or(false); if remove_data { - remove_user_data( - person.id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; + remove_user_data(person.id, &context).await?; } // Mod tables diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index bd8d9d386829467f0fabae6f57788cd4f044e217..7c59fd76bfd07b8cfca8537b556ea53ef12f3c91 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -33,24 +33,14 @@ impl Perform for PurgeCommunity { let community = Community::read(&mut context.pool(), community_id).await?; if let Some(banner) = community.banner { - purge_image_from_pictrs(context.client(), context.settings(), &banner) - .await - .ok(); + purge_image_from_pictrs(&banner, context).await.ok(); } if let Some(icon) = community.icon { - purge_image_from_pictrs(context.client(), context.settings(), &icon) - .await - .ok(); + purge_image_from_pictrs(&icon, context).await.ok(); } - purge_image_posts_for_community( - community_id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; + purge_image_posts_for_community(community_id, context).await?; Community::delete(&mut context.pool(), community_id).await?; diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index 838b36070d364611f8344f6280beb261eb3b7ec5..1100a0db9cad423ae2703ca9fde33d27df28bdc4 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -32,24 +32,14 @@ impl Perform for PurgePerson { let person = Person::read(&mut context.pool(), person_id).await?; if let Some(banner) = person.banner { - purge_image_from_pictrs(context.client(), context.settings(), &banner) - .await - .ok(); + purge_image_from_pictrs(&banner, context).await.ok(); } if let Some(avatar) = person.avatar { - purge_image_from_pictrs(context.client(), context.settings(), &avatar) - .await - .ok(); + purge_image_from_pictrs(&avatar, context).await.ok(); } - purge_image_posts_for_person( - person_id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; + purge_image_posts_for_person(person_id, context).await?; Person::delete(&mut context.pool(), person_id).await?; diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index ee0a3af094d95f179950e55bb00ed99de8e41f1a..b267f3518fb2d69c518b956413ad6478d7db4dfe 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -34,15 +34,11 @@ impl Perform for PurgePost { // Purge image if let Some(url) = post.url { - purge_image_from_pictrs(context.client(), context.settings(), &url) - .await - .ok(); + purge_image_from_pictrs(&url, context).await.ok(); } // Purge thumbnail if let Some(thumbnail_url) = post.thumbnail_url { - purge_image_from_pictrs(context.client(), context.settings(), &thumbnail_url) - .await - .ok(); + purge_image_from_pictrs(&thumbnail_url, context).await.ok(); } let community_id = post.community_id; diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index ef970f1bc5ae696fce95b07178d08128c493def4..695486a5a9f74be5f39ee34350c7598481eede06 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -365,6 +365,7 @@ pub struct CommentReplyResponse { /// Delete your account. pub struct DeleteAccount { pub password: Sensitive<String>, + pub delete_content: bool, pub auth: Sensitive<String>, } diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index b62514c02f22b3139ec4e3c268ff0ce15ba1bd5e..8fe302741e02998792ddf922a846cad07384925d 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -1,4 +1,4 @@ -use crate::post::SiteMetadata; +use crate::{context::LemmyContext, post::SiteMetadata}; use encoding::{all::encodings, DecoderTrap}; use lemmy_db_schema::newtypes::DbUrl; use lemmy_utils::{ @@ -150,12 +150,11 @@ pub(crate) async fn fetch_pictrs( /// - It might not be an image /// - Pictrs might not be set up pub async fn purge_image_from_pictrs( - client: &ClientWithMiddleware, - settings: &Settings, image_url: &Url, + context: &LemmyContext, ) -> Result<(), LemmyError> { - let pictrs_config = settings.pictrs_config()?; - is_image_content_type(client, image_url).await?; + let pictrs_config = context.settings().pictrs_config()?; + is_image_content_type(context.client(), image_url).await?; let alias = image_url .path_segments() @@ -168,7 +167,8 @@ pub async fn purge_image_from_pictrs( let pictrs_api_key = pictrs_config .api_key .ok_or(LemmyErrorType::PictrsApiKeyNotProvided)?; - let response = client + let response = context + .client() .post(&purge_url) .timeout(REQWEST_TIMEOUT) .header("x-api-token", pictrs_api_key) diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs index 1ba9aa646d1186ceac69914e426a2614c58d23cd..45109d5c53e6f51748c2f592f02da06da040c6e0 100644 --- a/crates/api_common/src/send_activity.rs +++ b/crates/api_common/src/send_activity.rs @@ -58,7 +58,7 @@ pub enum SendActivityData { CreatePrivateMessage(PrivateMessageView), UpdatePrivateMessage(PrivateMessageView), DeletePrivateMessage(Person, PrivateMessage, bool), - DeleteUser(Person), + DeleteUser(Person, bool), CreateReport(Url, Person, Community, String), } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index a79e3bd52013fa24f25f12b700cd41e0df06c27f..63095f41a2129107f8542e414d7efe2f61610218 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -42,7 +42,6 @@ use lemmy_utils::{ utils::slurs::build_slur_regex, }; use regex::Regex; -use reqwest_middleware::ClientWithMiddleware; use rosetta_i18n::{Language, LanguageId}; use tracing::warn; use url::{ParseError, Url}; @@ -529,19 +528,16 @@ pub fn check_private_instance_and_federation_enabled( pub async fn purge_image_posts_for_person( banned_person_id: PersonId, - pool: &mut DbPool<'_>, - settings: &Settings, - client: &ClientWithMiddleware, + context: &LemmyContext, ) -> Result<(), LemmyError> { + let pool = &mut context.pool(); let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?; for post in posts { if let Some(url) = post.url { - purge_image_from_pictrs(client, settings, &url).await.ok(); + purge_image_from_pictrs(&url, context).await.ok(); } if let Some(thumbnail_url) = post.thumbnail_url { - purge_image_from_pictrs(client, settings, &thumbnail_url) - .await - .ok(); + purge_image_from_pictrs(&thumbnail_url, context).await.ok(); } } @@ -552,19 +548,16 @@ pub async fn purge_image_posts_for_person( pub async fn purge_image_posts_for_community( banned_community_id: CommunityId, - pool: &mut DbPool<'_>, - settings: &Settings, - client: &ClientWithMiddleware, + context: &LemmyContext, ) -> Result<(), LemmyError> { + let pool = &mut context.pool(); let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?; for post in posts { if let Some(url) = post.url { - purge_image_from_pictrs(client, settings, &url).await.ok(); + purge_image_from_pictrs(&url, context).await.ok(); } if let Some(thumbnail_url) = post.thumbnail_url { - purge_image_from_pictrs(client, settings, &thumbnail_url) - .await - .ok(); + purge_image_from_pictrs(&thumbnail_url, context).await.ok(); } } @@ -575,21 +568,16 @@ pub async fn purge_image_posts_for_community( pub async fn remove_user_data( banned_person_id: PersonId, - pool: &mut DbPool<'_>, - settings: &Settings, - client: &ClientWithMiddleware, + context: &LemmyContext, ) -> Result<(), LemmyError> { + let pool = &mut context.pool(); // Purge user images let person = Person::read(pool, banned_person_id).await?; if let Some(avatar) = person.avatar { - purge_image_from_pictrs(client, settings, &avatar) - .await - .ok(); + purge_image_from_pictrs(&avatar, context).await.ok(); } if let Some(banner) = person.banner { - purge_image_from_pictrs(client, settings, &banner) - .await - .ok(); + purge_image_from_pictrs(&banner, context).await.ok(); } // Update the fields to None @@ -608,7 +596,7 @@ pub async fn remove_user_data( Post::update_removed_for_creator(pool, banned_person_id, None, true).await?; // Purge image posts - purge_image_posts_for_person(banned_person_id, pool, settings, client).await?; + purge_image_posts_for_person(banned_person_id, context).await?; // Communities // Remove all communities where they're the top mod @@ -635,12 +623,10 @@ pub async fn remove_user_data( // Delete the community images if let Some(icon) = first_mod_community.community.icon { - purge_image_from_pictrs(client, settings, &icon).await.ok(); + purge_image_from_pictrs(&icon, context).await.ok(); } if let Some(banner) = first_mod_community.community.banner { - purge_image_from_pictrs(client, settings, &banner) - .await - .ok(); + purge_image_from_pictrs(&banner, context).await.ok(); } // Update the fields to None Community::update( @@ -695,23 +681,18 @@ pub async fn remove_user_data_in_community( Ok(()) } -pub async fn delete_user_account( +pub async fn purge_user_account( person_id: PersonId, - pool: &mut DbPool<'_>, - settings: &Settings, - client: &ClientWithMiddleware, + context: &LemmyContext, ) -> Result<(), LemmyError> { + let pool = &mut context.pool(); // Delete their images let person = Person::read(pool, person_id).await?; if let Some(avatar) = person.avatar { - purge_image_from_pictrs(client, settings, &avatar) - .await - .ok(); + purge_image_from_pictrs(&avatar, context).await.ok(); } if let Some(banner) = person.banner { - purge_image_from_pictrs(client, settings, &banner) - .await - .ok(); + purge_image_from_pictrs(&banner, context).await.ok(); } // No need to update avatar and banner, those are handled in Person::delete_account @@ -726,7 +707,7 @@ pub async fn delete_user_account( .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; // Purge image posts - purge_image_posts_for_person(person_id, pool, settings, client).await?; + purge_image_posts_for_person(person_id, context).await?; // Leave communities they mod CommunityModerator::leave_all_communities(pool, person_id).await?; diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs index 94c547b14811f420d34c2bd7b3695da5dfac6f71..20796c34be75bf64bcb6f8685f654991c3286a89 100644 --- a/crates/api_crud/src/user/delete.rs +++ b/crates/api_crud/src/user/delete.rs @@ -5,8 +5,9 @@ use lemmy_api_common::{ context::LemmyContext, person::{DeleteAccount, DeleteAccountResponse}, send_activity::{ActivityChannel, SendActivityData}, - utils::local_user_view_from_jwt, + utils::{local_user_view_from_jwt, purge_user_account}, }; +use lemmy_db_schema::source::person::Person; use lemmy_utils::error::{LemmyError, LemmyErrorType}; #[tracing::instrument(skip(context))] @@ -26,8 +27,14 @@ pub async fn delete_account( return Err(LemmyErrorType::IncorrectLogin)?; } + if data.delete_content { + purge_user_account(local_user_view.person.id, &context).await?; + } else { + Person::delete_account(&mut context.pool(), local_user_view.person.id).await?; + } + ActivityChannel::submit_activity( - SendActivityData::DeleteUser(local_user_view.person), + SendActivityData::DeleteUser(local_user_view.person, data.delete_content), &context, ) .await?; diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index 163aca038e1cb4c195d1a4271472eadb4f9c7b99..7b14213cacab328c48963e231b0b0613fe5c6167 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -165,13 +165,7 @@ impl ActivityHandler for BlockUser { ) .await?; if self.remove_data.unwrap_or(false) { - remove_user_data( - blocked_person.id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; + remove_user_data(blocked_person.id, context).await?; } // write mod log diff --git a/crates/apub/src/activities/deletion/delete_user.rs b/crates/apub/src/activities/deletion/delete_user.rs index cf37dc5abbded4ba8b6137133685dfb1910a5283..66a402161b55706cfafeb2360e81f1007a42a74f 100644 --- a/crates/apub/src/activities/deletion/delete_user.rs +++ b/crates/apub/src/activities/deletion/delete_user.rs @@ -10,20 +10,17 @@ use activitypub_federation::{ protocol::verification::verify_urls_match, traits::{ActivityHandler, Actor}, }; -use lemmy_api_common::{context::LemmyContext, utils::delete_user_account}; +use lemmy_api_common::{context::LemmyContext, utils::purge_user_account}; use lemmy_db_schema::source::person::Person; use lemmy_utils::error::LemmyError; use url::Url; -pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<(), LemmyError> { +pub async fn delete_user( + person: Person, + delete_content: bool, + context: Data<LemmyContext>, +) -> Result<(), LemmyError> { let actor: ApubPerson = person.into(); - delete_user_account( - actor.id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; let id = generate_activity_id( DeleteType::Delete, @@ -36,6 +33,7 @@ pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result< kind: DeleteType::Delete, id: id.clone(), cc: vec![], + remove_data: Some(delete_content), }; let inboxes = remote_instance_inboxes(&mut context.pool()).await?; @@ -68,13 +66,11 @@ impl ActivityHandler for DeleteUser { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { let actor = self.actor.dereference(context).await?; - delete_user_account( - actor.id, - &mut context.pool(), - context.settings(), - context.client(), - ) - .await?; + if self.remove_data.unwrap_or(false) { + purge_user_account(actor.id, context).await?; + } else { + Person::delete_account(&mut context.pool(), actor.id).await?; + } Ok(()) } } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index ad04e861f7ffd4217496f87d1978101b5a706f7b..2fe0a82d171c435f50a4aa25d6e60e3946fc2b0b 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -335,7 +335,7 @@ pub async fn match_outgoing_activities( DeletePrivateMessage(person, pm, deleted) => { send_apub_delete_private_message(&person.into(), pm, deleted, context).await } - DeleteUser(person) => delete_user(person, context).await, + DeleteUser(person, delete_content) => delete_user(person, delete_content, context).await, CreateReport(url, actor, community, reason) => { Report::send(ObjectId::from(url), actor, community, reason, context).await } diff --git a/crates/apub/src/protocol/activities/deletion/delete_user.rs b/crates/apub/src/protocol/activities/deletion/delete_user.rs index f5f9908d5c6909379831a785ff402b1b11c4518b..46b070fab30692c4f99993012756475a02408a5e 100644 --- a/crates/apub/src/protocol/activities/deletion/delete_user.rs +++ b/crates/apub/src/protocol/activities/deletion/delete_user.rs @@ -23,4 +23,6 @@ pub struct DeleteUser { #[serde(deserialize_with = "deserialize_one_or_many", default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub(crate) cc: Vec<Url>, + /// Nonstandard field. If present, all content from the user should be deleted along with the account + pub(crate) remove_data: Option<bool>, }