summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/Cargo.lock8
-rw-r--r--server/Cargo.toml2
-rw-r--r--server/src/api/comment.rs16
-rw-r--r--server/src/api/community.rs21
-rw-r--r--server/src/api/post.rs16
-rw-r--r--server/src/apub/comment.rs187
-rw-r--r--server/src/apub/community.rs194
-rw-r--r--server/src/apub/fetcher.rs39
-rw-r--r--server/src/apub/mod.rs65
-rw-r--r--server/src/apub/post.rs188
-rw-r--r--server/src/apub/shared_inbox.rs946
-rw-r--r--server/src/apub/user.rs19
-rw-r--r--server/src/db/community.rs1
-rw-r--r--ui/src/api_tests/api.spec.ts555
14 files changed, 2212 insertions, 45 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 56e81b47..714422b6 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "activitystreams"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae98a55a86fc3150f278b225644cd46b5359f4d75067eae6dc3a52b409c537fb"
+checksum = "dd5b29a0f2c64cc56f2b79ec29cab68a9dab3b714d811a55668d072f18a8638e"
dependencies = [
"activitystreams-derive",
"chrono",
@@ -17,9 +17,9 @@ dependencies = [
[[package]]
name = "activitystreams-derive"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20d0384ae423a1df266f216e351ce9b40e8d369467d9242c086121154b4327dd"
+checksum = "985d3ca1ee226e83f4118e0235bc11d9fce39c4eec8d53739a21b01dd0b3f30f"
dependencies = [
"proc-macro2",
"quote",
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 2777dd2c..ab76d06f 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -8,7 +8,7 @@ edition = "2018"
diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
-activitystreams = "0.5.0-alpha.16"
+activitystreams = "0.6.0"
bcrypt = "0.6.2"
chrono = { version = "0.4.7", features = ["serde"] }
failure = "0.1.5"
diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs
index a6742e4c..2853beb3 100644
--- a/server/src/api/comment.rs
+++ b/server/src/api/comment.rs
@@ -337,7 +337,21 @@ impl Perform for Oper<EditComment> {
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
- updated_comment.send_update(&user, &conn)?;
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_comment.send_delete(&user, &conn)?;
+ } else {
+ updated_comment.send_undo_delete(&user, &conn)?;
+ }
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_comment.send_remove(&user, &conn)?;
+ } else {
+ updated_comment.send_undo_remove(&user, &conn)?;
+ }
+ } else {
+ updated_comment.send_update(&user, &conn)?;
+ }
let mut recipient_ids = Vec::new();
diff --git a/server/src/api/community.rs b/server/src/api/community.rs
index 296a77ea..9659469b 100644
--- a/server/src/api/community.rs
+++ b/server/src/api/community.rs
@@ -321,7 +321,8 @@ impl Perform for Oper<EditCommunity> {
let conn = pool.get()?;
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
@@ -358,7 +359,7 @@ impl Perform for Oper<EditCommunity> {
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()),
};
@@ -379,6 +380,20 @@ impl Perform for Oper<EditCommunity> {
ModRemoveCommunity::create(&conn, &form)?;
}
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_community.send_delete(&user, &conn)?;
+ } else {
+ updated_community.send_undo_delete(&user, &conn)?;
+ }
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_community.send_remove(&user, &conn)?;
+ } else {
+ updated_community.send_undo_remove(&user, &conn)?;
+ }
+ }
+
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
let res = CommunityResponse {
@@ -701,7 +716,7 @@ impl Perform for Oper<TransferCommunity> {
title: read_community.title,
description: read_community.description,
category_id: read_community.category_id,
- creator_id: data.user_id,
+ creator_id: data.user_id, // This makes the new user the community creator
removed: None,
deleted: None,
nsfw: read_community.nsfw,
diff --git a/server/src/api/post.rs b/server/src/api/post.rs
index 306365fa..b9c4c083 100644
--- a/server/src/api/post.rs
+++ b/server/src/api/post.rs
@@ -541,7 +541,21 @@ impl Perform for Oper<EditPost> {
ModStickyPost::create(&conn, &form)?;
}
- updated_post.send_update(&user, &conn)?;
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_post.send_delete(&user, &conn)?;
+ } else {
+ updated_post.send_undo_delete(&user, &conn)?;
+ }
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_post.send_remove(&user, &conn)?;
+ } else {
+ updated_post.send_undo_remove(&user, &conn)?;
+ }
+ } else {
+ updated_post.send_update(&user, &conn)?;
+ }
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
diff --git a/server/src/apub/comment.rs b/server/src/apub/comment.rs
index 6cede17b..87258275 100644
--- a/server/src/apub/comment.rs
+++ b/server/src/apub/comment.rs
@@ -35,6 +35,15 @@ impl ToApub for Comment {
Ok(comment)
}
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.ap_id,
+ self.updated,
+ NoteType.to_string(),
+ )
+ }
}
impl FromApub for CommentForm {
@@ -157,6 +166,184 @@ impl ApubObjectType for Comment {
)?;
Ok(())
}
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: creator.id,
+ data: serde_json::to_value(&delete)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ send_activity(
+ &delete,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+
+ // Generate a fake delete activity, with the correct object
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ &community.get_followers_url(),
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: creator.id,
+ data: serde_json::to_value(&undo)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ send_activity(
+ &undo,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: mod_.id,
+ data: serde_json::to_value(&remove)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ send_activity(
+ &remove,
+ &mod_.private_key.as_ref().unwrap(),
+ &mod_.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+
+ // Generate a fake delete activity, with the correct object
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ &community.get_followers_url(),
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(remove)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: mod_.id,
+ data: serde_json::to_value(&undo)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ send_activity(
+ &undo,
+ &mod_.private_key.as_ref().unwrap(),
+ &mod_.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
}
impl ApubLikeableType for Comment {
diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs
index 46bd9024..3510fbff 100644
--- a/server/src/apub/community.rs
+++ b/server/src/apub/community.rs
@@ -1,4 +1,5 @@
use super::*;
+use activitystreams::actor::kind::GroupType;
#[derive(Deserialize)]
pub struct CommunityQuery {
@@ -13,13 +14,21 @@ impl ToApub for Community {
let mut group = Group::default();
let oprops: &mut ObjectProperties = group.as_mut();
- let creator = User_::read(conn, self.creator_id)?;
+ // The attributed to, is an ordered vector with the creator actor_ids first,
+ // then the rest of the moderators
+ // TODO Technically the instance admins can mod the community, but lets
+ // ignore that for now
+ let moderators = CommunityModeratorView::for_community(&conn, self.id)?
+ .into_iter()
+ .map(|m| m.user_actor_id)
+ .collect();
+
oprops
.set_context_xsd_any_uri(context())?
.set_id(self.actor_id.to_owned())?
.set_name_xsd_string(self.name.to_owned())?
.set_published(convert_datetime(self.published))?
- .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+ .set_many_attributed_to_xsd_any_uris(moderators)?;
if let Some(u) = self.updated.to_owned() {
oprops.set_updated(convert_datetime(u))?;
@@ -45,6 +54,15 @@ impl ToApub for Community {
Ok(group.extend(actor_props).extend(self.get_public_key_ext()))
}
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.actor_id,
+ self.updated,
+ GroupType.to_string(),
+ )
+ }
}
impl ActorType for Community {
@@ -94,10 +112,162 @@ impl ActorType for Community {
Ok(())
}
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut delete = Delete::default();
+ populate_object_props(&mut delete.object_props, &self.get_followers_url(), &id)?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(group)?;
+
+ // 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)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(
+ &delete,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ self.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut delete = Delete::default();
+ populate_object_props(&mut delete.object_props, &self.get_followers_url(), &id)?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(group)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(&mut undo.object_props, &self.get_followers_url(), &undo_id)?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: self.creator_id,
+ data: serde_json::to_value(&undo)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(
+ &undo,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ self.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut remove = Remove::default();
+ populate_object_props(&mut remove.object_props, &self.get_followers_url(), &id)?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(group)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: mod_.id,
+ data: serde_json::to_value(&remove)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(
+ &remove,
+ &mod_.private_key.as_ref().unwrap(),
+ &mod_.actor_id,
+ self.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut remove = Remove::default();
+ populate_object_props(&mut remove.object_props, &self.get_followers_url(), &id)?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(group)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(&mut undo.object_props, &self.get_followers_url(), &undo_id)?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(remove)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: mod_.id,
+ data: serde_json::to_value(&undo)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for remove , the creator is the actor, and does the signing
+ send_activity(
+ &undo,
+ &mod_.private_key.as_ref().unwrap(),
+ &mod_.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<Vec<String>, Error> {
- debug!("got here.");
-
Ok(
CommunityFollowerView::for_community(conn, self.id)?
.into_iter()
@@ -135,8 +305,11 @@ impl FromApub for CommunityForm {
// TODO don't do extra fetching here
// let _outbox = fetch_remote_object::<OrderedCollection>(&outbox_uri)?;
// let _followers = fetch_remote_object::<UnorderedCollection>(&followers_uri)?;
- let apub_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
- let creator = get_or_fetch_and_upsert_remote_user(&apub_id, conn)?;
+ let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap();
+ let creator = creator_and_moderator_uris
+ .next()
+ .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
+ .unwrap();
Ok(CommunityForm {
name: oprops.get_name_xsd_string().unwrap().to_string(),
@@ -170,8 +343,13 @@ pub async fn get_apub_community_http(
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
- let c = community.to_apub(&db.get().unwrap())?;
- Ok(create_apub_response(&c))
+ if !community.deleted {
+ Ok(create_apub_response(
+ &community.to_apub(&db.get().unwrap())?,
+ ))
+ } else {
+ Ok(create_apub_tombstone_response(&community.to_tombstone()?))
+ }
}
/// Returns an empty followers collection, only populating the siz (for privacy).
diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs
index 8e82a2f7..8a4b8855 100644
--- a/server/src/apub/fetcher.rs
+++ b/server/src/apub/fetcher.rs
@@ -111,10 +111,10 @@ pub fn get_or_fetch_and_upsert_remote_user(
match User_::read_from_actor_id(&conn, &apub_id) {
Ok(u) => {
// If its older than a day, re-fetch it
- // TODO the less than needs to be tested
- if u
- .last_refreshed_at
- .lt(&(naive_now() - chrono::Duration::days(1)))
+ if !u.local
+ && u
+ .last_refreshed_at
+ .lt(&(naive_now() - chrono::Duration::days(1)))
{
debug!("Fetching and updating from remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
@@ -143,10 +143,10 @@ pub fn get_or_fetch_and_upsert_remote_community(
match Community::read_from_actor_id(&conn, &apub_id) {
Ok(c) => {
// If its older than a day, re-fetch it
- // TODO the less than needs to be tested
- if c
- .last_refreshed_at
- .lt(&(naive_now() - chrono::Duration::days(1)))
+ if !c.local
+ && c
+ .last_refreshed_at
+ .lt(&(naive_now() - chrono::Duration::days(1)))
{
debug!("Fetching and updating from remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
@@ -161,7 +161,28 @@ pub fn get_or_fetch_and_upsert_remote_community(
debug!("Fetching and creating remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
let cf = CommunityForm::from_apub(&group, conn)?;
- Ok(Community::create(conn, &cf)?)
+ let community = Community::create(conn, &cf)?;
+
+ // Also add the community moderators too
+ let creator_and_moderator_uris = group
+ .base
+ .base
+ .object_props
+ .get_many_attributed_to_xsd_any_uris()
+ .unwrap();
+ let creator_and_moderators = creator_and_moderator_uris
+ .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
+ .collect::<Vec<User_>>();
+
+ for mod_ in creator_and_moderators {
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: community.id,
+ user_id: mod_.id,
+ };
+ CommunityModerator::join(&conn, &community_moderator_form)?;
+ }
+
+ Ok(community)
}
Err(e) => Err(Error::from(e)),
}
diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs
index 4b08c53a..3c18a013 100644
--- a/server/src/apub/mod.rs
+++ b/server/src/apub/mod.rs
@@ -9,14 +9,17 @@ pub mod signatures;
pub mod user;
pub mod user_inbox;
+use crate::api::community::CommunityResponse;
+use crate::websocket::server::SendCommunityRoomMessage;
+use activitystreams::object::kind::{NoteType, PageType};
use activitystreams::{
- activity::{Accept, Create, Dislike, Follow, Like, Update},
+ activity::{Accept, Create, Delete, Dislike, Follow, Like, Remove, Undo, 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;
@@ -44,13 +47,16 @@ use crate::api::post::PostResponse;
use crate::api::site::SearchResponse;
use crate::db::comment::{Comment, CommentForm, CommentLike, CommentLikeForm};
use crate::db::comment_view::CommentView;
-use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
-use crate::db::community_view::{CommunityFollowerView, CommunityView};
+use crate::db::community::{
+ Community, CommunityFollower, CommunityFollowerForm, CommunityForm, CommunityModerator,
+ CommunityModeratorForm,
+};
+use crate::db::community_view::{CommunityFollowerView, CommunityModeratorView, CommunityView};
use crate::db::post::{Post, PostForm, PostLike, PostLikeForm};
use crate::db::post_view::PostView;
use crate::db::user::{UserForm, User_};
use crate::db::user_view::UserView;
-use crate::db::{activity, Crud, Followable, Likeable, SearchType};
+use crate::db::{activity, Crud, Followable, Joinable, Likeable, SearchType};
use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
use crate::routes::{ChatServerParam, DbPoolParam};
use crate::websocket::{
@@ -60,6 +66,7 @@ use crate::websocket::{
use crate::{convert_datetime, naive_now, Settings};
use activities::{populate_object_props, send_activity};
+use chrono::NaiveDateTime;
use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user};
use signatures::verify;
use signatures::{sign, PublicKey, PublicKeyExtension};
@@ -87,6 +94,15 @@ where
.json(data)
}
+fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
+where
+ T: Serialize,
+{
+ HttpResponse::Gone()
+ .content_type(APUB_JSON_CONTENT_TYPE)
+ .json(data)
+}
+
/// Generates the ActivityPub ID for a given object type and name.
///
/// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
@@ -142,6 +158,34 @@ fn is_apub_id_valid(apub_id: &Url) -> bool {
pub trait ToApub {
type Response;
fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
+ fn to_tombstone(&self) -> Result<Tombstone, Error>;
+}
+
+fn create_tombstone(
+ deleted: bool,
+ object_id: &str,
+ updated: Option<NaiveDateTime>,
+ former_type: String,
+) -> Result<Tombstone, Error> {
+ if deleted {
+ if let Some(updated) = updated {
+ let mut tombstone = Tombstone::default();
+ tombstone.object_props.set_id(object_id)?;
+ tombstone
+ .tombstone_props
+ .set_former_type_xsd_string(former_type)?
+ .set_deleted(convert_datetime(updated))?;
+ Ok(tombstone)
+ } else {
+ Err(format_err!(
+ "Cant convert to tombstone because updated time was None."
+ ))
+ }
+ } else {
+ Err(format_err!(
+ "Cant convert object to tombstone if it wasnt deleted"
+ ))
+ }
}
pub trait FromApub {
@@ -154,11 +198,16 @@ 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>;
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
}
pub trait ApubLikeableType {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ // TODO add send_undo_like / undo_dislike
}
pub fn get_shared_inbox(actor_id: &str) -> String {
@@ -192,6 +241,12 @@ pub trait ActorType {
Err(format_err!("Accept not implemented."))
}
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_remove(&self, mod_: &User_, 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 af8ee599..2d1f1c71 100644
--- a/server/src/apub/post.rs
+++ b/server/src/apub/post.rs
@@ -12,7 +12,11 @@ pub async fn get_apub_post(
) -> Result<HttpResponse<Body>, Error> {
let id = info.post_id.parse::<i32>()?;
let post = Post::read(&&db.get()?, id)?;
- Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?))
+ if !post.deleted {
+ Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?))
+ } else {
+ Ok(create_apub_tombstone_response(&post.to_tombstone()?))
+ }
}
impl ToApub for Post {
@@ -53,6 +57,15 @@ impl ToApub for Post {
Ok(page)
}
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.ap_id,
+ self.updated,
+ PageType.to_string(),
+ )
+ }
}
impl FromApub for PostForm {
@@ -163,6 +176,179 @@ impl ApubObjectType for Post {
)?;
Ok(())
}
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(page)?;
+
+ // 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)?;
+
+ let community = Community::read(conn, self.community_id)?;
+ send_activity(
+ &delete,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ &community.get_followers_url(),
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(page)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ &community.get_followers_url(),
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ // Insert the sent activity into the activity table
+ let activity_form = activity::ActivityForm {
+ user_id: self.creator_id,
+ data: serde_json::to_value(&undo)?,
+ local: true,
+ updated: None,
+ };
+ activity::Activity::create(&conn, &activity_form)?;
+
+ let community = Community::read(conn, self.community_id)?;
+ send_activity(
+ &undo,
+ &creator.private_key.as_ref().unwrap(),
+ &creator.actor_id,
+ community.get_follower_inboxes(&conn)?,
+ )?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ p