From 0c0c68398609d549a757a9c3a26ce2311075fa38 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 28 Apr 2020 19:46:25 +0200 Subject: [PATCH] Implement deleting communities --- server/src/api/community.rs | 7 +++- server/src/apub/comment.rs | 12 +++--- server/src/apub/community.rs | 46 ++++++++++++++++++++-- server/src/apub/mod.rs | 30 +++++++++++++-- server/src/apub/post.rs | 12 +++--- server/src/apub/shared_inbox.rs | 68 +++++++++++++++++++++++++++++++++ server/src/apub/user.rs | 10 ++++- 7 files changed, 163 insertions(+), 22 deletions(-) diff --git a/server/src/api/community.rs b/server/src/api/community.rs index 296a77eaf..7610d1b78 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -358,7 +358,7 @@ impl Perform for Oper { published: None, }; - let _updated_community = match Community::update(&conn, data.edit_id, &community_form) { + let updated_community = match Community::update(&conn, data.edit_id, &community_form) { Ok(community) => community, Err(_e) => return Err(APIError::err("couldnt_update_community").into()), }; @@ -377,6 +377,11 @@ impl Perform for Oper { expires, }; ModRemoveCommunity::create(&conn, &form)?; + updated_community.send_delete(&conn)?; + } + + if let Some(_deleted) = data.deleted.to_owned() { + updated_community.send_delete(&conn)?; } let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?; diff --git a/server/src/apub/comment.rs b/server/src/apub/comment.rs index 6cede17b2..9fa5731ba 100644 --- a/server/src/apub/comment.rs +++ b/server/src/apub/comment.rs @@ -3,7 +3,7 @@ use super::*; impl ToApub for Comment { type Response = Note; - fn to_apub(&self, conn: &PgConnection) -> Result { + fn to_apub(&self, conn: &PgConnection) -> Result, Error> { let mut comment = Note::default(); let oprops: &mut ObjectProperties = comment.as_mut(); let creator = User_::read(&conn, self.creator_id)?; @@ -33,7 +33,7 @@ impl ToApub for Comment { oprops.set_updated(convert_datetime(u))?; } - Ok(comment) + Ok(ResponseOrTombstone::Response(comment)) } } @@ -102,7 +102,7 @@ impl ApubObjectType for Comment { create .create_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(note)?; + .set_object_base_box(note.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -138,7 +138,7 @@ impl ApubObjectType for Comment { update .update_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(note)?; + .set_object_base_box(note.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -171,7 +171,7 @@ impl ApubLikeableType for Comment { like .like_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(note)?; + .set_object_base_box(note.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -206,7 +206,7 @@ impl ApubLikeableType for Comment { dislike .dislike_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(note)?; + .set_object_base_box(note.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 46bd9024c..ee3199954 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -9,7 +9,17 @@ impl ToApub for Community { type Response = GroupExt; // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. - fn to_apub(&self, conn: &PgConnection) -> Result { + fn to_apub(&self, conn: &PgConnection) -> Result, Error> { + if self.deleted || self.removed { + let mut tombstone = Tombstone::default(); + // TODO: might want to include updated/deleted times as well + tombstone + .object_props + .set_id(self.actor_id.to_owned())? + .set_published(convert_datetime(self.published))?; + return Ok(ResponseOrTombstone::Tombstone(Box::new(tombstone))); + } + let mut group = Group::default(); let oprops: &mut ObjectProperties = group.as_mut(); @@ -43,7 +53,9 @@ impl ToApub for Community { .set_endpoints(endpoint_props)? .set_followers(self.get_followers_url())?; - Ok(group.extend(actor_props).extend(self.get_public_key_ext())) + Ok(ResponseOrTombstone::Response( + group.extend(actor_props).extend(self.get_public_key_ext()), + )) } } @@ -94,10 +106,36 @@ impl ActorType for Community { Ok(()) } + fn send_delete(&self, conn: &PgConnection) -> Result<(), Error> { + let community = self.to_apub(conn)?; + let mut delete = Delete::default(); + delete + .delete_props + .set_actor_xsd_any_uri(self.actor_id.to_owned())? + .set_object_base_box(BaseBox::from_concrete( + community.as_tombstone()?.to_owned(), + )?)?; + + // Insert the sent activity into the activity table + let activity_form = activity::ActivityForm { + user_id: self.creator_id, + data: serde_json::to_value(&delete)?, + local: true, + updated: None, + }; + activity::Activity::create(&conn, &activity_form)?; + + send_activity( + &delete, + &self.private_key.to_owned().unwrap(), + &self.actor_id, + self.get_follower_inboxes(&conn)?, + )?; + Ok(()) + } + /// For a given community, returns the inboxes of all followers. fn get_follower_inboxes(&self, conn: &PgConnection) -> Result, Error> { - debug!("got here."); - Ok( CommunityFollowerView::for_community(conn, self.id)? .into_iter() diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 4b08c53aa..9232c2d7e 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -10,13 +10,13 @@ pub mod user; pub mod user_inbox; use activitystreams::{ - activity::{Accept, Create, Dislike, Follow, Like, Update}, + activity::{Accept, Create, Delete, Dislike, Follow, Like, Update}, actor::{properties::ApActorProperties, Actor, Group, Person}, collection::UnorderedCollection, context, endpoint::EndpointProperties, ext::{Ext, Extensible, Extension}, - object::{properties::ObjectProperties, Note, Page}, + object::{properties::ObjectProperties, Note, Page, Tombstone}, public, BaseBox, }; use actix_web::body::Body; @@ -138,10 +138,31 @@ fn is_apub_id_valid(apub_id: &Url) -> bool { } } +#[derive(Serialize)] +pub enum ResponseOrTombstone { + Response(Response), + Tombstone(Box), +} + +impl ResponseOrTombstone { + fn as_response(&self) -> Result<&Response, Error> { + match self { + ResponseOrTombstone::Response(r) => Ok(r), + ResponseOrTombstone::Tombstone(_t) => Err(format_err!("Value is a tombstone")), + } + } + fn as_tombstone(&self) -> Result<&Tombstone, Error> { + match self { + ResponseOrTombstone::Tombstone(t) => Ok(t), + ResponseOrTombstone::Response(_r) => Err(format_err!("Value is a response")), + } + } +} + // TODO Not sure good names for these pub trait ToApub { type Response; - fn to_apub(&self, conn: &PgConnection) -> Result; + fn to_apub(&self, conn: &PgConnection) -> Result, Error>; } pub trait FromApub { @@ -154,6 +175,7 @@ pub trait FromApub { pub trait ApubObjectType { fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; + //fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; } pub trait ApubLikeableType { @@ -192,6 +214,8 @@ pub trait ActorType { Err(format_err!("Accept not implemented.")) } + fn send_delete(&self, conn: &PgConnection) -> Result<(), Error>; + // TODO default because there is no user following yet. #[allow(unused_variables)] /// For a given community, returns the inboxes of all followers. diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index af8ee5998..381ba3c6c 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -19,7 +19,7 @@ impl ToApub for Post { type Response = Page; // Turn a Lemmy post into an ActivityPub page that can be sent out over the network. - fn to_apub(&self, conn: &PgConnection) -> Result { + fn to_apub(&self, conn: &PgConnection) -> Result, Error> { let mut page = Page::default(); let oprops: &mut ObjectProperties = page.as_mut(); let creator = User_::read(conn, self.creator_id)?; @@ -51,7 +51,7 @@ impl ToApub for Post { oprops.set_updated(convert_datetime(u))?; } - Ok(page) + Ok(ResponseOrTombstone::Response(page)) } } @@ -109,7 +109,7 @@ impl ApubObjectType for Post { create .create_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(page)?; + .set_object_base_box(page.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -144,7 +144,7 @@ impl ApubObjectType for Post { update .update_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(page)?; + .set_object_base_box(page.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -176,7 +176,7 @@ impl ApubLikeableType for Post { like .like_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(page)?; + .set_object_base_box(page.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { @@ -210,7 +210,7 @@ impl ApubLikeableType for Post { dislike .dislike_props .set_actor_xsd_any_uri(creator.actor_id.to_owned())? - .set_object_base_box(page)?; + .set_object_base_box(page.as_response()?.to_owned())?; // Insert the sent activity into the activity table let activity_form = activity::ActivityForm { diff --git a/server/src/apub/shared_inbox.rs b/server/src/apub/shared_inbox.rs index 705a578a0..10ed1122d 100644 --- a/server/src/apub/shared_inbox.rs +++ b/server/src/apub/shared_inbox.rs @@ -1,4 +1,6 @@ use super::*; +use crate::api::community::CommunityResponse; +use crate::websocket::server::SendCommunityRoomMessage; #[serde(untagged)] #[derive(Serialize, Deserialize, Debug)] @@ -7,6 +9,7 @@ pub enum SharedAcceptedObjects { Update(Update), Like(Like), Dislike(Dislike), + Delete(Delete), } impl SharedAcceptedObjects { @@ -16,6 +19,7 @@ impl SharedAcceptedObjects { SharedAcceptedObjects::Update(u) => u.update_props.get_object_base_box(), SharedAcceptedObjects::Like(l) => l.like_props.get_object_base_box(), SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_object_base_box(), + SharedAcceptedObjects::Delete(d) => d.delete_props.get_object_base_box(), } } } @@ -61,6 +65,9 @@ pub async fn shared_inbox( (SharedAcceptedObjects::Dislike(d), Some("Note")) => { receive_dislike_comment(&d, &request, &conn, chat_server) } + (SharedAcceptedObjects::Delete(d), Some("Tombstone")) => { + receive_delete_community(&d, &request, &conn, chat_server) + } _ => Err(format_err!("Unknown incoming activity type.")), } } @@ -502,3 +509,64 @@ fn receive_dislike_comment( Ok(HttpResponse::Ok().finish()) } + +fn receive_delete_community( + delete: &Delete, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result { + let tombstone = delete + .delete_props + .get_object_base_box() + .to_owned() + .unwrap() + .to_owned() + .to_concrete::()?; + let community_apub_id = tombstone.object_props.get_id().unwrap().to_string(); + + let community = Community::read_from_actor_id(conn, &community_apub_id)?; + verify(request, &community.public_key.clone().unwrap())?; + + // Insert the received activity into the activity table + let activity_form = activity::ActivityForm { + user_id: community.creator_id, + data: serde_json::to_value(&delete)?, + local: false, + updated: None, + }; + activity::Activity::create(&conn, &activity_form)?; + + let community_form = CommunityForm { + name: "".to_string(), + title: "".to_string(), + description: None, + category_id: community.category_id, // Note: need to keep this due to foreign key constraint + creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint + removed: None, + published: None, + updated: None, + deleted: Some(true), + nsfw: false, + actor_id: community.actor_id, + local: false, + private_key: None, + public_key: community.public_key, + last_refreshed_at: Some(community.last_refreshed_at), + }; + + Community::update(conn, community.id, &community_form)?; + + let res = CommunityResponse { + community: CommunityView::read(&conn, community.id, None)?, + }; + + chat_server.do_send(SendCommunityRoomMessage { + op: UserOperation::EditCommunity, + response: res, + community_id: community.id, + my_id: None, + }); + + Ok(HttpResponse::Ok().finish()) +} diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs index d7fd22826..36147f7a0 100644 --- a/server/src/apub/user.rs +++ b/server/src/apub/user.rs @@ -9,7 +9,7 @@ impl ToApub for User_ { type Response = PersonExt; // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network. - fn to_apub(&self, _conn: &PgConnection) -> Result { + fn to_apub(&self, _conn: &PgConnection) -> Result, Error> { // TODO go through all these to_string and to_owned() let mut person = Person::default(); let oprops: &mut ObjectProperties = person.as_mut(); @@ -41,7 +41,9 @@ impl ToApub for User_ { .set_following(self.get_following_url())? .set_liked(self.get_liked_url())?; - Ok(person.extend(actor_props).extend(self.get_public_key_ext())) + Ok(ResponseOrTombstone::Response( + person.extend(actor_props).extend(self.get_public_key_ext()), + )) } } @@ -87,6 +89,10 @@ impl ActorType for User_ { )?; Ok(()) } + + fn send_delete(&self, _conn: &PgConnection) -> Result<(), Error> { + unimplemented!() + } } impl FromApub for UserForm {