From 40dbfc05a2c463917309627820ae467823424949 Mon Sep 17 00:00:00 2001 From: silverpill Date: Sat, 4 Dec 2021 00:42:36 +0000 Subject: [PATCH] Add API method for deleting posts --- README.md | 1 + docs/openapi.yaml | 13 ++++++++ src/activitypub/activity.rs | 23 ++++++++++++++ src/activitypub/vocabulary.rs | 1 + src/mastodon_api/statuses/views.rs | 48 +++++++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fd3fe1..d0b3738 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ GET /api/v1/notifications GET /api/v2/search POST /api/v1/statuses GET /api/v1/statuses/{status_id} +DELETE /api/v1/statuses/{status_id} GET /api/v1/statuses/{status_id}/context POST /api/v1/statuses/{status_id}/favourite POST /api/v1/statuses/{status_id}/unfavourite diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 66c13b6..6189c1c 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -5,6 +5,19 @@ info: version: 1.0.0 paths: + /api/v1/statuses/{status_id}: + delete: + summary: Delete post + parameters: + - $ref: '#/components/parameters/status_id' + responses: + 204: + description: Successful operation + content: {} + 403: + description: Post does not belong to user + 404: + description: Post not found /api/v1/{status_id}/make_permanent: post: summary: Save post to IPFS diff --git a/src/activitypub/activity.rs b/src/activitypub/activity.rs index 2ad8702..ef92856 100644 --- a/src/activitypub/activity.rs +++ b/src/activitypub/activity.rs @@ -249,6 +249,29 @@ pub fn create_activity_announce( activity } +pub fn create_activity_delete_note( + instance_url: &str, + actor_profile: &DbActorProfile, + post: &Post, +) -> Activity { + let object_id = post.get_object_id(instance_url); + let object = Object { + context: Some(json!(AP_CONTEXT)), + id: object_id, + object_type: TOMBSTONE.to_string(), + ..Default::default() + }; + let activity = create_activity( + instance_url, + &actor_profile.username, + DELETE, + None, + object, + AP_PUBLIC, + ); + activity +} + pub fn create_activity_follow( instance_url: &str, actor_profile: &DbActorProfile, diff --git a/src/activitypub/vocabulary.rs b/src/activitypub/vocabulary.rs index 3e052f8..7cd1e47 100644 --- a/src/activitypub/vocabulary.rs +++ b/src/activitypub/vocabulary.rs @@ -21,6 +21,7 @@ pub const DOCUMENT: &str = "Document"; pub const IMAGE: &str = "Image"; pub const MENTION: &str = "Mention"; pub const NOTE: &str = "Note"; +pub const TOMBSTONE: &str = "Tombstone"; // Misc pub const PROPERTY_VALUE: &str = "PropertyValue"; diff --git a/src/mastodon_api/statuses/views.rs b/src/mastodon_api/statuses/views.rs index 02d38c1..31608e8 100644 --- a/src/mastodon_api/statuses/views.rs +++ b/src/mastodon_api/statuses/views.rs @@ -1,5 +1,5 @@ /// https://docs.joinmastodon.org/methods/statuses/ -use actix_web::{get, post, web, HttpResponse, Scope}; +use actix_web::{delete, get, post, web, HttpResponse, Scope}; use actix_web_httpauth::extractors::bearer::BearerAuth; use serde::Serialize; use uuid::Uuid; @@ -8,6 +8,7 @@ use crate::activitypub::activity::{ create_activity_note, create_activity_like, create_activity_announce, + create_activity_delete_note, }; use crate::activitypub::actor::Actor; use crate::activitypub::deliverer::deliver_activity; @@ -31,6 +32,7 @@ use crate::models::posts::queries::{ create_post, get_post_by_id, get_thread, + get_post_author, find_reposts_by_user, update_post, delete_post, @@ -135,6 +137,49 @@ async fn get_status( Ok(HttpResponse::Ok().json(status)) } +#[delete("/{status_id}")] +async fn delete_status( + auth: BearerAuth, + config: web::Data, + db_pool: web::Data, + web::Path(status_id): web::Path, +) -> Result { + let db_client = &mut **get_database_client(&db_pool).await?; + let current_user = get_current_user(db_client, auth.token()).await?; + let post = get_post_by_id(db_client, &status_id).await?; + if post.author.id != current_user.id { + return Err(HttpError::PermissionError); + }; + let deletion_queue = delete_post(db_client, &status_id).await?; + let config_clone = config.clone(); + actix_rt::spawn(async move { + deletion_queue.process(&config_clone).await; + }); + + let activity = create_activity_delete_note( + &config.instance_url(), + ¤t_user.profile, + &post, + ); + let mut audience = get_followers(db_client, ¤t_user.id).await?; + if let Some(in_reply_to_id) = post.in_reply_to_id { + let in_reply_to_author = get_post_author(db_client, &in_reply_to_id).await?; + audience.push(in_reply_to_author); + }; + audience.extend(post.mentions); + let mut recipients: Vec = Vec::new(); + for profile in audience { + let maybe_remote_actor = profile.remote_actor() + .map_err(|_| HttpError::InternalError)?; + if let Some(remote_actor) = maybe_remote_actor { + recipients.push(remote_actor); + }; + }; + deliver_activity(&config, ¤t_user, activity, recipients); + + Ok(HttpResponse::NoContent().finish()) +} + #[get("/{status_id}/context")] async fn get_context( auth: Option, @@ -426,6 +471,7 @@ pub fn status_api_scope() -> Scope { .service(create_status) // Routes with status ID .service(get_status) + .service(delete_status) .service(get_context) .service(favourite) .service(unfavourite)