From ab8aa2cbe1e1625d64f6feb12319abdf4d986ed0 Mon Sep 17 00:00:00 2001 From: asonix Date: Wed, 6 Jan 2021 02:21:17 -0600 Subject: [PATCH] Profiles: expose needed information for non-apub profile creation - Add image purge to profile update - Add outbound processing in apub ingest - Add follower + follow request cleanup in profile delete - Add inboxes + id to Outbound trait --- profiles/Cargo.toml | 1 - profiles/src/apub/actions/apub/mod.rs | 5 +- profiles/src/apub/actions/apub/person.rs | 16 ++- profiles/src/apub/actions/block.rs | 7 +- profiles/src/apub/actions/comment.rs | 10 +- profiles/src/apub/actions/follow.rs | 10 +- profiles/src/apub/actions/follow_request.rs | 12 +- profiles/src/apub/actions/mod.rs | 87 +++++++++++- profiles/src/apub/actions/profile.rs | 120 +++++++++++----- profiles/src/apub/actions/react.rs | 8 +- profiles/src/apub/actions/submission.rs | 10 +- profiles/src/apub/mod.rs | 13 +- profiles/src/apub/results/block.rs | 36 ++++- profiles/src/apub/results/comment.rs | 149 ++++++++++++++++++-- profiles/src/apub/results/follow_request.rs | 94 ++++++++++-- profiles/src/apub/results/mod.rs | 14 ++ profiles/src/apub/results/profile.rs | 75 ++++++++-- profiles/src/apub/results/react.rs | 56 +++++++- profiles/src/apub/results/submission.rs | 77 ++++++++-- profiles/src/lib.rs | 33 ++++- profiles/src/pictrs.rs | 13 +- profiles/src/store/mod.rs | 18 +-- 22 files changed, 721 insertions(+), 143 deletions(-) diff --git a/profiles/Cargo.toml b/profiles/Cargo.toml index 0441e38..bbfe6df 100644 --- a/profiles/Cargo.toml +++ b/profiles/Cargo.toml @@ -12,7 +12,6 @@ actix-web = "3.3.2" activitystreams = "0.7.0-alpha.9" activitystreams-ext = "0.1.0-alpha.2" chrono = { version = "0.4.19", features = ["serde"] } -hyaenidae-toolkit = { version = "0.1.0", path = "../toolkit" } log = "0.4.11" mime = "0.3.16" rand = "0.7.0" diff --git a/profiles/src/apub/actions/apub/mod.rs b/profiles/src/apub/actions/apub/mod.rs index eeed2c3..e2339fe 100644 --- a/profiles/src/apub/actions/apub/mod.rs +++ b/profiles/src/apub/actions/apub/mod.rs @@ -25,7 +25,9 @@ pub(crate) fn ingest( match parse(any_base.clone(), context)? { Ok(action) => { - action.perform(context)?; + if let Some(outbound) = action.perform(context)? { + context.deliver(&*outbound); + } if let Some(any_base) = stack.pop() { context.spawner.process(any_base, stack); } @@ -39,6 +41,7 @@ pub(crate) fn ingest( context.spawner.download_images(urls, stack); } } + Ok(()) } diff --git a/profiles/src/apub/actions/apub/person.rs b/profiles/src/apub/actions/apub/person.rs index 56ef55a..aadca8d 100644 --- a/profiles/src/apub/actions/apub/person.rs +++ b/profiles/src/apub/actions/apub/person.rs @@ -1,5 +1,5 @@ use crate::{ - apub::actions::{CreateProfile, DeleteProfile, UpdateProfile}, + apub::actions::{CreateProfile, CreateProfileApub, DeleteProfile, UpdateProfile}, apub::ExtendedPerson, recover, store::OwnerSource, @@ -82,7 +82,11 @@ pub(crate) fn person( .unwrap_or(true); Ok(Ok(Box::new(CreateProfile { - person_apub_id: Some(id.to_owned()), + apub: Some(CreateProfileApub { + person_apub_id: id.to_owned(), + public_key_id, + public_key, + }), owner_source: OwnerSource::Remote(id.to_string()), handle, domain, @@ -91,8 +95,6 @@ pub(crate) fn person( login_required, icon, banner, - public_key_id, - public_key, published, }))) } @@ -153,11 +155,11 @@ pub(crate) fn update_person( profile_id, display_name, description, - login_required, + login_required: Some(login_required), icon, banner, - public_key_id, - public_key, + public_key_id: Some(public_key_id), + public_key: Some(public_key), }))) } diff --git a/profiles/src/apub/actions/block.rs b/profiles/src/apub/actions/block.rs index 602caa6..58061fd 100644 --- a/profiles/src/apub/actions/block.rs +++ b/profiles/src/apub/actions/block.rs @@ -1,10 +1,10 @@ use crate::{ apub::actions::{CreateBlock, DeleteBlock, RejectFollowRequest, UndoFollow}, - Action, Completed, Context, Error, Required, + Action, Context, Error, Outbound, Required, }; impl Action for CreateBlock { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let block = context.store.view.blocks.new( self.blocked_profile, self.blocked_by_profile, @@ -88,7 +88,7 @@ impl Action for CreateBlock { } impl Action for DeleteBlock { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context.store.view.blocks.remove(self.block_id)?; let block_apub_id = context.apub.apub_for_block(self.block_id)?.req()?; context.apub.delete_object(&block_apub_id)?; @@ -98,6 +98,7 @@ impl Action for DeleteBlock { return Ok(Some(Box::new(crate::apub::results::UndoBlock { block_apub_id, profile_id: undo_block.0.right, + blocked_id: undo_block.0.left, }))); } } diff --git a/profiles/src/apub/actions/comment.rs b/profiles/src/apub/actions/comment.rs index c78e64c..70e9d44 100644 --- a/profiles/src/apub/actions/comment.rs +++ b/profiles/src/apub/actions/comment.rs @@ -1,10 +1,10 @@ use crate::{ apub::actions::{AnnounceComment, CreateComment, DeleteComment, DeleteReact, UpdateComment}, - Action, Completed, Context, Error, Required, + Action, Outbound, Context, Error, Required, }; impl Action for CreateComment { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let submissioner_id = context .store .submissions @@ -79,13 +79,13 @@ impl Action for CreateComment { } impl Action for AnnounceComment { - fn perform(&self, _: &Context) -> Result>, Error> { + fn perform(&self, _: &Context) -> Result>, Error> { Ok(None) } } impl Action for UpdateComment { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let comment = context.store.comments.by_id(self.comment_id)?.req()?; let mut changes = comment.update(); @@ -126,7 +126,7 @@ fn delete_react(react_id: uuid::Uuid, context: &Context) -> Result<(), Error> { } impl Action for DeleteComment { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context.store.comments.delete(self.comment_id)?; context.store.view.comments.remove(self.comment_id); context.apub.delete_object(&self.note_apub_id)?; diff --git a/profiles/src/apub/actions/follow.rs b/profiles/src/apub/actions/follow.rs index 6cf074d..3dce8c5 100644 --- a/profiles/src/apub/actions/follow.rs +++ b/profiles/src/apub/actions/follow.rs @@ -1,10 +1,10 @@ use crate::{ apub::actions::{UndoAcceptFollow, UndoFollow}, - Action, Completed, Context, Error, Required, + Action, Context, Error, Outbound, Required, }; impl Action for UndoFollow { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context.store.view.follows.remove(self.follow_id)?; let accept_apub_id = context.apub.apub_for_follow(self.follow_id)?.req()?; context.apub.delete_object(&accept_apub_id)?; @@ -15,6 +15,7 @@ impl Action for UndoFollow { return Ok(Some(Box::new(crate::apub::results::UndoFollow { follow_apub_id: self.follow_apub_id.clone(), profile_id: undo_follow.0.right, + followed_id: undo_follow.0.left, }))); } } @@ -24,7 +25,7 @@ impl Action for UndoFollow { } impl Action for UndoAcceptFollow { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context.store.view.follows.remove(self.follow_id)?; let accept_apub_id = context.apub.apub_for_follow(self.follow_id)?.req()?; context.apub.delete_object(&accept_apub_id)?; @@ -34,7 +35,8 @@ impl Action for UndoAcceptFollow { if context.is_local(undo_follow.0.right)? { return Ok(Some(Box::new(crate::apub::results::UndoAcceptFollow { accept_apub_id, - profile_id: undo_follow.0.right, + profile_id: undo_follow.0.left, + requester_id: undo_follow.0.right, }))); } } diff --git a/profiles/src/apub/actions/follow_request.rs b/profiles/src/apub/actions/follow_request.rs index 392c6a2..717e2bf 100644 --- a/profiles/src/apub/actions/follow_request.rs +++ b/profiles/src/apub/actions/follow_request.rs @@ -2,11 +2,11 @@ use crate::{ apub::actions::{ AcceptFollowRequest, CreateFollowRequest, RejectFollowRequest, UndoFollowRequest, }, - Action, Completed, Context, Error, Required, + Action, Context, Error, Outbound, Required, }; impl Action for CreateFollowRequest { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { context.check_block(self.followed_by_profile, self.followed_profile)?; let follow_request = context.store.view.follow_requests.new( @@ -40,7 +40,7 @@ impl Action for CreateFollowRequest { } impl Action for AcceptFollowRequest { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context .store .view @@ -82,7 +82,7 @@ impl Action for AcceptFollowRequest { } impl Action for RejectFollowRequest { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context .store .view @@ -108,6 +108,7 @@ impl Action for RejectFollowRequest { return Ok(Some(Box::new(crate::apub::results::RejectFollow { follow_apub_id, profile_id: undo_follow_request.0.left, + requester_id: undo_follow_request.0.right, }))); } } @@ -117,7 +118,7 @@ impl Action for RejectFollowRequest { } impl Action for UndoFollowRequest { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let opt = context .store .view @@ -143,6 +144,7 @@ impl Action for UndoFollowRequest { return Ok(Some(Box::new(crate::apub::results::UndoFollow { follow_apub_id, profile_id: undo_follow_request.0.right, + followed_id: undo_follow_request.0.left, }))); } } diff --git a/profiles/src/apub/actions/mod.rs b/profiles/src/apub/actions/mod.rs index f098c14..66a6f0a 100644 --- a/profiles/src/apub/actions/mod.rs +++ b/profiles/src/apub/actions/mod.rs @@ -72,8 +72,14 @@ pub struct DeleteSubmission { submission_id: Uuid, } +pub struct CreateProfileApub { + person_apub_id: Url, + public_key_id: Url, + public_key: String, +} + pub struct CreateProfile { - person_apub_id: Option, + apub: Option, owner_source: OwnerSource, handle: String, domain: String, @@ -82,20 +88,89 @@ pub struct CreateProfile { login_required: bool, icon: Option, banner: Option, - public_key_id: Url, - public_key: String, published: DateTime, } +impl CreateProfile { + pub fn from_local(owner_id: Uuid, handle: String, domain: String) -> Self { + CreateProfile { + apub: None, + owner_source: OwnerSource::Local(owner_id), + handle, + domain, + display_name: None, + description: None, + login_required: true, + icon: None, + banner: None, + published: Utc::now(), + } + } +} + pub struct UpdateProfile { profile_id: Uuid, display_name: Option, description: Option, - login_required: bool, + login_required: Option, icon: Option, banner: Option, - public_key_id: Url, - public_key: String, + public_key_id: Option, + public_key: Option, +} + +impl UpdateProfile { + pub fn from_text(profile_id: Uuid, display_name: String, description: String) -> Self { + UpdateProfile { + profile_id, + display_name: Some(display_name), + description: Some(description), + login_required: None, + icon: None, + banner: None, + public_key_id: None, + public_key: None, + } + } + + pub fn from_icon(profile_id: Uuid, icon: Uuid) -> Self { + UpdateProfile { + profile_id, + display_name: None, + description: None, + login_required: None, + icon: Some(icon), + banner: None, + public_key_id: None, + public_key: None, + } + } + + pub fn from_banner(profile_id: Uuid, banner: Uuid) -> Self { + UpdateProfile { + profile_id, + display_name: None, + description: None, + login_required: None, + icon: None, + banner: Some(banner), + public_key_id: None, + public_key: None, + } + } + + pub fn from_login_required(profile_id: Uuid, login_required: bool) -> Self { + UpdateProfile { + profile_id, + display_name: None, + description: None, + login_required: Some(login_required), + icon: None, + banner: None, + public_key_id: None, + public_key: None, + } + } } pub struct DeleteProfile { diff --git a/profiles/src/apub/actions/profile.rs b/profiles/src/apub/actions/profile.rs index cd692c0..28a1d50 100644 --- a/profiles/src/apub/actions/profile.rs +++ b/profiles/src/apub/actions/profile.rs @@ -1,11 +1,11 @@ use crate::{ apub::actions::{CreateProfile, DeleteProfile, DeleteSubmission, UpdateProfile}, - Action, Completed, Context, Error, Required, + Action, Context, Error, Outbound, Required, }; impl Action for CreateProfile { - fn perform(&self, context: &Context) -> Result>, Error> { - let profile = context.store.profiles.create( + fn perform(&self, ctx: &Context) -> Result>, Error> { + let profile = ctx.store.profiles.create( self.owner_source.clone(), &self.handle, &self.domain, @@ -20,27 +20,26 @@ impl Action for CreateProfile { if let Some(description) = &self.description { changes.description(description); } - let profile = context.store.profiles.update(&changes)?; + let profile = ctx.store.profiles.update(&changes)?; let mut changes = profile.update_images(); if let Some(banner) = self.banner { - if let Ok(Some(file)) = context.store.files.by_id(banner) { + if let Ok(Some(file)) = ctx.store.files.by_id(banner) { changes.banner(&file); } } if let Some(icon) = self.icon { - if let Ok(Some(file)) = context.store.files.by_id(icon) { + if let Ok(Some(file)) = ctx.store.files.by_id(icon) { changes.banner(&file); } } - let profile = context.store.profiles.update_images(&changes)?; + let profile = ctx.store.profiles.update_images(&changes)?; - context - .apub - .store_public_key(profile.id(), &self.public_key_id, &self.public_key)?; + if let Some(apub) = &self.apub { + ctx.apub.profile(&apub.person_apub_id, profile.id())?; - if let Some(apub_id) = &self.person_apub_id { - context.apub.profile(apub_id, profile.id())?; + ctx.apub + .store_public_key(profile.id(), &apub.public_key_id, &apub.public_key)?; } if profile.owner_source().is_local() { @@ -54,34 +53,51 @@ impl Action for CreateProfile { } impl Action for UpdateProfile { - fn perform(&self, context: &Context) -> Result>, Error> { - let profile = context.store.profiles.by_id(self.profile_id)?.req()?; + fn perform(&self, ctx: &Context) -> Result>, Error> { + let profile = ctx.store.profiles.by_id(self.profile_id)?.req()?; let mut changes = profile.update(); - changes.login_required(self.login_required); + if let Some(login_required) = self.login_required { + changes.login_required(login_required); + } if let Some(display_name) = &self.display_name { changes.display_name(display_name); } if let Some(description) = &self.description { changes.description(description); } - let profile = context.store.profiles.update(&changes)?; + let profile = ctx.store.profiles.update(&changes)?; + + let previous_banner = profile.banner(); + let previous_icon = profile.icon(); let mut changes = profile.update_images(); if let Some(banner) = self.banner { - if let Ok(Some(file)) = context.store.files.by_id(banner) { + if let Ok(Some(file)) = ctx.store.files.by_id(banner) { changes.banner(&file); + if let Some(old_banner) = previous_banner { + if old_banner != banner { + ctx.spawner.purge_file(old_banner); + } + } } } if let Some(icon) = self.icon { - if let Ok(Some(file)) = context.store.files.by_id(icon) { - changes.banner(&file); + if let Ok(Some(file)) = ctx.store.files.by_id(icon) { + changes.icon(&file); + if let Some(old_icon) = previous_icon { + if old_icon != icon { + ctx.spawner.purge_file(old_icon); + } + } + } + } + let profile = ctx.store.profiles.update_images(&changes)?; + if let Some(id) = &self.public_key_id { + if let Some(key) = &self.public_key { + ctx.apub.store_public_key(profile.id(), id, key)?; } } - let profile = context.store.profiles.update_images(&changes)?; - context - .apub - .store_public_key(profile.id(), &self.public_key_id, &self.public_key)?; if profile.owner_source().is_local() { return Ok(Some(Box::new(crate::apub::results::ProfileUpdated { @@ -93,30 +109,29 @@ impl Action for UpdateProfile { } } -fn delete_submission(submission_id: uuid::Uuid, context: &Context) -> Result<(), Error> { - Action::perform(&DeleteSubmission { submission_id }, context)?; +fn delete_submission(submission_id: uuid::Uuid, ctx: &Context) -> Result<(), Error> { + Action::perform(&DeleteSubmission { submission_id }, ctx)?; Ok(()) } impl Action for DeleteProfile { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, ctx: &Context) -> Result>, Error> { let profile_id = self.profile_id; - let opt = context.store.profiles.delete(profile_id)?; + let opt = ctx.store.profiles.delete(profile_id)?; if let Some(undo_profile) = opt { - let person_apub_id = context.apub.apub_for_profile(profile_id)?.req()?; - context.apub.delete_object(&person_apub_id)?; + let person_apub_id = ctx.apub.apub_for_profile(profile_id)?.req()?; + ctx.apub.delete_object(&person_apub_id)?; if let Some(banner) = undo_profile.0.banner() { - context.spawner.purge_file(banner); + ctx.spawner.purge_file(banner); } if let Some(icon) = undo_profile.0.icon() { - context.spawner.purge_file(icon); + ctx.spawner.purge_file(icon); } - let context_clone = context.clone(); - - context.spawn_blocking(move || { + let context_clone = ctx.clone(); + ctx.spawn_blocking(move || { for submission_id in context_clone.store.submissions.for_profile(profile_id) { if let Err(e) = delete_submission(submission_id, &context_clone) { log::error!("Failed to delete submission {}: {}", submission_id, e); @@ -124,9 +139,46 @@ impl Action for DeleteProfile { } }); + let follower_ids = ctx + .store + .view + .follows + .forward_iter(profile_id) + .filter_map(|follow_id| ctx.store.view.follows.right(follow_id).ok()?) + .collect::>(); + + let context_clone = ctx.clone(); + ctx.spawn_blocking(move || { + for follow_id in context_clone.store.view.follows.forward_iter(profile_id) { + if let Err(e) = context_clone.store.view.follows.remove(follow_id) { + log::error!("Failed to delete follow {}: {}", follow_id, e); + } + } + }); + + let context_clone = ctx.clone(); + ctx.spawn_blocking(move || { + for follow_request_id in context_clone + .store + .view + .follow_requests + .forward_iter(profile_id) + { + if let Err(e) = context_clone + .store + .view + .follow_requests + .remove(follow_request_id) + { + log::error!("Failed to delete follow {}: {}", follow_request_id, e); + } + } + }); + if undo_profile.0.owner_source().is_local() { return Ok(Some(Box::new(crate::apub::results::ProfileDeleted { profile_id, + follower_ids, }))); } } diff --git a/profiles/src/apub/actions/react.rs b/profiles/src/apub/actions/react.rs index 17bc7dd..7f9fc15 100644 --- a/profiles/src/apub/actions/react.rs +++ b/profiles/src/apub/actions/react.rs @@ -1,10 +1,10 @@ use crate::{ apub::actions::{CreateReact, DeleteReact}, - Action, Completed, Context, Error, Required, + Action, Context, Error, Outbound, Required, }; impl Action for CreateReact { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let submissioner_id = context .store .submissions @@ -62,7 +62,7 @@ impl Action for CreateReact { } impl Action for DeleteReact { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let react_id = self.react_id; let opt = context.store.reacts.delete(react_id)?; @@ -78,6 +78,8 @@ impl Action for DeleteReact { return Ok(Some(Box::new(crate::apub::results::UndoReact { like_apub_id, profile_id, + submission_id: undo_react.0.submission_id(), + comment_id: undo_react.0.comment_id(), }))); } } diff --git a/profiles/src/apub/actions/submission.rs b/profiles/src/apub/actions/submission.rs index bbe24ef..8763900 100644 --- a/profiles/src/apub/actions/submission.rs +++ b/profiles/src/apub/actions/submission.rs @@ -2,12 +2,12 @@ use crate::{ apub::actions::{ AnnounceSubmission, CreateSubmission, DeleteComment, DeleteSubmission, UpdateSubmission, }, - Action, Completed, Context, Error, Required, + Action, Outbound, Context, Error, Required, }; use std::collections::HashSet; impl Action for CreateSubmission { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let submission = context .store @@ -60,13 +60,13 @@ impl Action for CreateSubmission { } impl Action for AnnounceSubmission { - fn perform(&self, _: &Context) -> Result>, Error> { + fn perform(&self, _: &Context) -> Result>, Error> { Ok(None) } } impl Action for UpdateSubmission { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let submission_id = self.submission_id; let submission = context.store.submissions.by_id(submission_id)?.req()?; @@ -116,7 +116,7 @@ fn delete_comment(comment_id: uuid::Uuid, context: &Context) -> Result<(), Error } impl Action for DeleteSubmission { - fn perform(&self, context: &Context) -> Result>, Error> { + fn perform(&self, context: &Context) -> Result>, Error> { let submission_id = self.submission_id; let opt = context.store.submissions.delete(submission_id)?; diff --git a/profiles/src/apub/mod.rs b/profiles/src/apub/mod.rs index 51fc4d6..1bd1891 100644 --- a/profiles/src/apub/mod.rs +++ b/profiles/src/apub/mod.rs @@ -4,15 +4,15 @@ use std::{fmt, sync::Arc}; use url::Url; use uuid::Uuid; -mod actions; +pub mod actions; mod keys; -pub mod results; +mod results; pub(crate) use actions::ingest; use keys::{ExtendedPerson, PublicKey, PublicKeyInner}; pub trait ApubIds { - fn gen_id(&self) -> Url; + fn gen_id(&self) -> Option; fn public_key(&self, id: &Url) -> Option; fn following(&self, id: &Url) -> Option; @@ -237,6 +237,13 @@ impl Store { Ok(()) } + pub fn serve_object(&self, id: &Url) -> Result, crate::Error> { + if self.deleted(id)? { + return Ok(None); + } + Ok(self.object(id)?) + } + fn deleted(&self, id: &Url) -> Result { Ok(self.deleted.get(id.as_str())?.is_some()) } diff --git a/profiles/src/apub/results/block.rs b/profiles/src/apub/results/block.rs index 876afcd..c575666 100644 --- a/profiles/src/apub/results/block.rs +++ b/profiles/src/apub/results/block.rs @@ -1,5 +1,5 @@ use super::{Block as Blocked, UndoBlock}; -use crate::{Completed, Context, Error, Required}; +use crate::{Context, Error, Outbound, Required}; use activitystreams::{ activity::{Block, Undo}, base::AnyBase, @@ -7,8 +7,21 @@ use activitystreams::{ prelude::*, security, }; +use url::Url; +use uuid::Uuid; + +impl Outbound for Blocked { + fn id(&self) -> Option { + Some(self.block_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let block = ctx.store.view.blocks.by_id(self.block_id)?.req()?; + let inbox = ctx.apub.endpoints_for_profile(block.left)?.req()?.inbox; + + Ok(vec![inbox]) + } -impl Completed for Blocked { fn to_apub(&self, ctx: &Context) -> Result { let block = ctx.store.view.blocks.by_id(self.block_id)?.req()?; let actor_id = ctx.apub.apub_for_profile(block.right)?.req()?; @@ -17,7 +30,7 @@ impl Completed for Blocked { let mut block = Block::new(actor_id, object_id); block - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .set_published(published.into()) .add_context(context()) .add_context(security()); @@ -28,13 +41,26 @@ impl Completed for Blocked { } } -impl Completed for UndoBlock { +impl Outbound for UndoBlock { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let inbox = ctx + .apub + .endpoints_for_profile(self.blocked_id)? + .req()? + .inbox; + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let block = ctx.apub.object(&self.block_apub_id)?.req()?; let mut undo = Undo::new(person_id, block); - undo.set_id(ctx.apub.info.gen_id()) + undo.set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let undo = undo.into_any_base()?; diff --git a/profiles/src/apub/results/comment.rs b/profiles/src/apub/results/comment.rs index 2a4c48e..975dc8f 100644 --- a/profiles/src/apub/results/comment.rs +++ b/profiles/src/apub/results/comment.rs @@ -1,7 +1,8 @@ use super::{ CommentCreated, CommentDeleted, CommentUpdated, RemoteCommentCreated, RemoteCommentUpdated, + RemoteCommenteDeleted, }; -use crate::{store::Comment, Completed, Context, Error, Required}; +use crate::{store::Comment, Context, Error, Outbound, Required}; use activitystreams::{ activity::{Announce, Create, Delete, Update}, base::AnyBase, @@ -10,7 +11,62 @@ use activitystreams::{ prelude::*, public, security, }; +use std::collections::HashSet; use url::Url; +use uuid::Uuid; + +fn local_inboxes( + submission_id: Uuid, + comment_id: Option, + ctx: &Context, +) -> Result, Error> { + let mut urls = vec![]; + + let submissioner_id = ctx + .store + .submissions + .by_id(submission_id)? + .req()? + .profile_id(); + let submissioner_inbox = ctx + .apub + .endpoints_for_profile(submissioner_id)? + .req()? + .inbox; + + urls.push(submissioner_inbox); + + if let Some(comment_id) = comment_id { + let commenter_id = ctx.store.comments.by_id(comment_id)?.req()?.profile_id(); + let commenter_inbox = ctx.apub.endpoints_for_profile(commenter_id)?.req()?.inbox; + + urls.push(commenter_inbox); + } + + Ok(urls) +} + +fn remote_inboxes(submissioner_id: Uuid, ctx: &Context) -> Result, Error> { + let follower_inboxes = ctx + .store + .view + .follows + .forward_iter(submissioner_id) + .filter_map(|follow_id| ctx.store.view.follows.right(follow_id).ok()) + .filter_map(|opt| opt) + .filter(|follower_id| ctx.is_local(*follower_id).unwrap_or(false)) + .filter_map(|follower_id| { + Some( + ctx.apub + .endpoints_for_profile(follower_id) + .ok()?? + .shared_inbox, + ) + }) + .collect::>(); + + Ok(follower_inboxes.into_iter().collect()) +} fn build_comment( comment: Comment, @@ -42,17 +98,27 @@ fn build_comment( Ok(any_base) } -impl Completed for CommentCreated { +impl Outbound for CommentCreated { + fn id(&self) -> Option { + Some(self.comment_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let comment = ctx.store.comments.by_id(self.comment_id)?.req()?; + + local_inboxes(comment.submission_id(), comment.comment_id(), ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let comment = ctx.store.comments.by_id(self.comment_id)?.req()?; let person_id = ctx.apub.apub_for_profile(comment.profile_id())?.req()?; - let note_id = ctx.apub.info.gen_id(); + let note_id = ctx.apub.info.gen_id().req()?; ctx.apub.comment(¬e_id, self.comment_id)?; let note = build_comment(comment, note_id, person_id.clone(), ctx)?; let mut create = Create::new(person_id, note); create - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = create.into_any_base()?; @@ -62,14 +128,22 @@ impl Completed for CommentCreated { } } -impl Completed for RemoteCommentCreated { +impl Outbound for RemoteCommentCreated { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + remote_inboxes(self.profile_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let comment = ctx.apub.object(&self.note_apub_id)?.req()?; let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let mut announce = Announce::new(person_id, comment); announce - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let announce = announce.into_any_base()?; @@ -79,7 +153,17 @@ impl Completed for RemoteCommentCreated { } } -impl Completed for CommentUpdated { +impl Outbound for CommentUpdated { + fn id(&self) -> Option { + Some(self.comment_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let comment = ctx.store.comments.by_id(self.comment_id)?.req()?; + + local_inboxes(comment.submission_id(), comment.comment_id(), ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let comment = ctx.store.comments.by_id(self.comment_id)?.req()?; let person_id = ctx.apub.apub_for_profile(comment.profile_id())?.req()?; @@ -88,7 +172,7 @@ impl Completed for CommentUpdated { let mut update = Update::new(person_id, note); update - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = update.into_any_base()?; @@ -98,14 +182,22 @@ impl Completed for CommentUpdated { } } -impl Completed for RemoteCommentUpdated { +impl Outbound for RemoteCommentUpdated { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + remote_inboxes(self.profile_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let update = ctx.apub.object(&self.update_apub_id)?.req()?; let mut announce = Announce::new(person_id, update); announce - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let announce = announce.into_any_base()?; @@ -115,14 +207,22 @@ impl Completed for RemoteCommentUpdated { } } -impl Completed for CommentDeleted { +impl Outbound for CommentDeleted { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + local_inboxes(self.submission_id, self.reply_to_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let note = ctx.apub.object(&self.note_apub_id)?.req()?; let mut delete = Delete::new(person_id, note); delete - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = delete.into_any_base()?; @@ -131,3 +231,28 @@ impl Completed for CommentDeleted { Ok(any_base) } } + +impl Outbound for RemoteCommenteDeleted { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + remote_inboxes(self.profile_id, ctx) + } + + fn to_apub(&self, ctx: &Context) -> Result { + let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; + let delete = ctx.apub.object(&self.delete_apub_id)?.req()?; + + let mut announce = Announce::new(person_id, delete); + announce + .set_id(ctx.apub.info.gen_id().req()?) + .add_context(context()) + .add_context(security()); + let announce = announce.into_any_base()?; + ctx.apub.store_object(&announce)?; + + Ok(announce) + } +} diff --git a/profiles/src/apub/results/follow_request.rs b/profiles/src/apub/results/follow_request.rs index 43f2675..22b256a 100644 --- a/profiles/src/apub/results/follow_request.rs +++ b/profiles/src/apub/results/follow_request.rs @@ -1,5 +1,5 @@ use super::{AcceptFollow, Follow as FollowRequested, RejectFollow, UndoAcceptFollow, UndoFollow}; -use crate::{Completed, Context, Error, Required}; +use crate::{Context, Error, Outbound, Required}; use activitystreams::{ activity::{Accept, Follow, Reject, Undo}, base::AnyBase, @@ -7,8 +7,26 @@ use activitystreams::{ prelude::*, security, }; +use url::Url; +use uuid::Uuid; + +impl Outbound for FollowRequested { + fn id(&self) -> Option { + Some(self.follow_request_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let follow = ctx + .store + .view + .follow_requests + .by_id(self.follow_request_id)? + .req()?; + let inbox = ctx.apub.endpoints_for_profile(follow.left)?.req()?.inbox; + + Ok(vec![inbox]) + } -impl Completed for FollowRequested { fn to_apub(&self, ctx: &Context) -> Result { let follow = ctx .store @@ -20,7 +38,7 @@ impl Completed for FollowRequested { let object_id = ctx.apub.apub_for_profile(follow.left)?.req()?; let published = follow.published; - let follow_id = ctx.apub.info.gen_id(); + let follow_id = ctx.apub.info.gen_id().req()?; ctx.apub .follow_request(&follow_id, self.follow_request_id)?; @@ -37,14 +55,28 @@ impl Completed for FollowRequested { } } -impl Completed for RejectFollow { +impl Outbound for RejectFollow { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let inbox = ctx + .apub + .endpoints_for_profile(self.requester_id)? + .req()? + .inbox; + + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let follow_request = ctx.apub.object(&self.follow_apub_id)?.req()?; let mut reject = Reject::new(person_id, follow_request); reject - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let reject = reject.into_any_base()?; @@ -54,12 +86,24 @@ impl Completed for RejectFollow { } } -impl Completed for AcceptFollow { +impl Outbound for AcceptFollow { + fn id(&self) -> Option { + Some(self.follow_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let requester_id = ctx.store.view.follows.right(self.follow_id)?.req()?; + + let inbox = ctx.apub.endpoints_for_profile(requester_id)?.req()?.inbox; + + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let follow_request = ctx.apub.object(&self.follow_apub_id)?.req()?; - let accept_id = ctx.apub.info.gen_id(); + let accept_id = ctx.apub.info.gen_id().req()?; ctx.apub.follow(&accept_id, self.follow_id)?; let mut accept = Accept::new(person_id, follow_request); @@ -74,13 +118,27 @@ impl Completed for AcceptFollow { } } -impl Completed for UndoAcceptFollow { +impl Outbound for UndoAcceptFollow { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let inbox = ctx + .apub + .endpoints_for_profile(self.requester_id)? + .req()? + .inbox; + + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let accept = ctx.apub.object(&self.accept_apub_id)?.req()?; let mut undo = Undo::new(person_id, accept); - undo.set_id(ctx.apub.info.gen_id()) + undo.set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let undo = undo.into_any_base()?; @@ -90,13 +148,27 @@ impl Completed for UndoAcceptFollow { } } -impl Completed for UndoFollow { +impl Outbound for UndoFollow { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let inbox = ctx + .apub + .endpoints_for_profile(self.followed_id)? + .req()? + .inbox; + + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let follow = ctx.apub.object(&self.follow_apub_id)?.req()?; let mut undo = Undo::new(person_id, follow); - undo.set_id(ctx.apub.info.gen_id()) + undo.set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let undo = undo.into_any_base()?; diff --git a/profiles/src/apub/results/mod.rs b/profiles/src/apub/results/mod.rs index e4c2851..3d1bd1b 100644 --- a/profiles/src/apub/results/mod.rs +++ b/profiles/src/apub/results/mod.rs @@ -18,6 +18,7 @@ pub(super) struct ProfileUpdated { pub(super) struct ProfileDeleted { pub(super) profile_id: Uuid, + pub(super) follower_ids: Vec, } pub(super) struct SubmissionCreated { @@ -54,6 +55,13 @@ pub(super) struct RemoteCommentUpdated { pub(super) struct CommentDeleted { pub(super) note_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) submission_id: Uuid, + pub(super) reply_to_id: Option, +} + +pub(super) struct RemoteCommenteDeleted { + pub(super) delete_apub_id: Url, + pub(super) profile_id: Uuid, } pub(super) struct React { @@ -63,6 +71,8 @@ pub(super) struct React { pub(super) struct UndoReact { pub(super) like_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) submission_id: Uuid, + pub(super) comment_id: Option, } pub(super) struct Follow { @@ -72,6 +82,7 @@ pub(super) struct Follow { pub(super) struct RejectFollow { pub(super) follow_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) requester_id: Uuid, } pub(super) struct AcceptFollow { @@ -83,11 +94,13 @@ pub(super) struct AcceptFollow { pub(super) struct UndoAcceptFollow { pub(super) accept_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) requester_id: Uuid, } pub(super) struct UndoFollow { pub(super) follow_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) followed_id: Uuid, } pub(super) struct Block { @@ -97,4 +110,5 @@ pub(super) struct Block { pub(super) struct UndoBlock { pub(super) block_apub_id: Url, pub(super) profile_id: Uuid, + pub(super) blocked_id: Uuid, } diff --git a/profiles/src/apub/results/profile.rs b/profiles/src/apub/results/profile.rs index d4437b0..5f33c63 100644 --- a/profiles/src/apub/results/profile.rs +++ b/profiles/src/apub/results/profile.rs @@ -2,7 +2,7 @@ use super::{ProfileCreated, ProfileDeleted, ProfileUpdated}; use crate::{ apub::{ExtendedPerson, PublicKey, PublicKeyInner}, store::FileSource, - Completed, Context, Error, Required, + Context, Error, Outbound, Required, }; use activitystreams::{ activity::{Create, Delete, Update}, @@ -12,12 +12,24 @@ use activitystreams::{ prelude::*, public, security, }; +use std::collections::HashSet; +use url::Url; +use uuid::Uuid; + +impl Outbound for ProfileCreated { + fn id(&self) -> Option { + Some(self.profile_id) + } + + fn inboxes(&self, _: &Context) -> Result, Error> { + // TODO: Potentially send to Server Actors of federated servers + Ok(vec![]) + } -impl Completed for ProfileCreated { fn to_apub(&self, ctx: &Context) -> Result { let profile = ctx.store.profiles.by_id(self.profile_id)?.req()?; - let person_id = ctx.apub.info.gen_id(); + let person_id = ctx.apub.info.gen_id().req()?; let public_key_id = ctx.apub.info.public_key(&person_id).req()?; let following = ctx.apub.info.following(&person_id).req()?; let followers = ctx.apub.info.followers(&person_id).req()?; @@ -88,7 +100,7 @@ impl Completed for ProfileCreated { let mut create = Create::new(person_id, any_base); create - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); @@ -99,7 +111,33 @@ impl Completed for ProfileCreated { } } -impl Completed for ProfileUpdated { +impl Outbound for ProfileUpdated { + fn id(&self) -> Option { + Some(self.profile_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let follower_inboxes = ctx + .store + .view + .follows + .forward_iter(self.profile_id) + .filter_map(|follow_id| ctx.store.view.follows.right(follow_id).ok()) + .filter_map(|opt| opt) + .filter(|follower_id| ctx.is_local(*follower_id).unwrap_or(false)) + .filter_map(|follower_id| { + Some( + ctx.apub + .endpoints_for_profile(follower_id) + .ok()?? + .shared_inbox, + ) + }) + .collect::>(); + + Ok(follower_inboxes.into_iter().collect()) + } + fn to_apub(&self, ctx: &Context) -> Result { let profile = ctx.store.profiles.by_id(self.profile_id)?.req()?; let endpoints = ctx.apub.endpoints_for_profile(self.profile_id)?.req()?; @@ -155,7 +193,7 @@ impl Completed for ProfileUpdated { let mut update = Update::new(person_id, any_base); update - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); @@ -166,14 +204,35 @@ impl Completed for ProfileUpdated { } } -impl Completed for ProfileDeleted { +impl Outbound for ProfileDeleted { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let inboxes = self + .follower_ids + .iter() + .filter_map(|follower_id| { + Some( + ctx.apub + .endpoints_for_profile(*follower_id) + .ok()?? + .shared_inbox, + ) + }) + .collect::>(); + + Ok(inboxes.into_iter().collect()) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let person = ctx.apub.object(&person_id)?.req()?; let mut delete = Delete::new(person_id, person); delete - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); diff --git a/profiles/src/apub/results/react.rs b/profiles/src/apub/results/react.rs index ecb6de5..7c1f043 100644 --- a/profiles/src/apub/results/react.rs +++ b/profiles/src/apub/results/react.rs @@ -1,5 +1,5 @@ use super::{React, UndoReact}; -use crate::{Completed, Context, Error, Required}; +use crate::{Context, Error, Outbound, Required}; use activitystreams::{ activity::{Create, Delete, Like}, base::AnyBase, @@ -7,8 +7,32 @@ use activitystreams::{ prelude::*, public, security, }; +use url::Url; +use uuid::Uuid; + +impl Outbound for React { + fn id(&self) -> Option { + Some(self.react_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let react = ctx.store.reacts.by_id(self.react_id)?.req()?; + + let notifier_id = if let Some(comment_id) = react.comment_id() { + ctx.store.comments.by_id(comment_id)?.req()?.profile_id() + } else { + ctx.store + .submissions + .by_id(react.submission_id())? + .req()? + .profile_id() + }; + + let inbox = ctx.apub.endpoints_for_profile(notifier_id)?.req()?.inbox; + + Ok(vec![inbox]) + } -impl Completed for React { fn to_apub(&self, ctx: &Context) -> Result { let react = ctx.store.reacts.by_id(self.react_id)?.req()?; let person_id = ctx.apub.apub_for_profile(react.profile_id())?.req()?; @@ -23,7 +47,7 @@ impl Completed for React { let endpoints = ctx.apub.endpoints_for_profile(react.profile_id())?.req()?; let mut like = Like::new(person_id.clone(), note_id); - like.set_id(ctx.apub.info.gen_id()) + like.set_id(ctx.apub.info.gen_id().req()?) .set_content(react.react()) .set_published(published.into()) .set_attributed_to(person_id.clone()) @@ -35,7 +59,7 @@ impl Completed for React { let mut create = Create::new(person_id, like); create - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); @@ -46,14 +70,34 @@ impl Completed for React { } } -impl Completed for UndoReact { +impl Outbound for UndoReact { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let notifier_id = if let Some(comment_id) = self.comment_id { + ctx.store.comments.by_id(comment_id)?.req()?.profile_id() + } else { + ctx.store + .submissions + .by_id(self.submission_id)? + .req()? + .profile_id() + }; + + let inbox = ctx.apub.endpoints_for_profile(notifier_id)?.req()?.inbox; + + Ok(vec![inbox]) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let like = ctx.apub.object(&self.like_apub_id)?.req()?; let mut delete = Delete::new(person_id, like); delete - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let delete = delete.into_any_base()?; diff --git a/profiles/src/apub/results/submission.rs b/profiles/src/apub/results/submission.rs index b53617b..b8b844b 100644 --- a/profiles/src/apub/results/submission.rs +++ b/profiles/src/apub/results/submission.rs @@ -1,7 +1,7 @@ use super::{SubmissionCreated, SubmissionDeleted, SubmissionUpdated}; use crate::{ store::{FileSource, Submission, Visibility}, - Completed, Context, Error, Required, + Context, Error, Outbound, Required, }; use activitystreams::{ activity::{Create, Delete, Update}, @@ -11,7 +11,30 @@ use activitystreams::{ prelude::*, public, security, }; +use std::collections::HashSet; use url::Url; +use uuid::Uuid; + +fn remote_inboxes(submissioner_id: Uuid, ctx: &Context) -> Result, Error> { + let follower_inboxes = ctx + .store + .view + .follows + .forward_iter(submissioner_id) + .filter_map(|follow_id| ctx.store.view.follows.right(follow_id).ok()) + .filter_map(|opt| opt) + .filter_map(|follower_id| { + Some( + ctx.apub + .endpoints_for_profile(follower_id) + .ok()?? + .shared_inbox, + ) + }) + .collect::>(); + + Ok(follower_inboxes.into_iter().collect()) +} fn build_submission( submission: Submission, @@ -58,16 +81,31 @@ fn build_submission( Ok(any_base) } -impl Completed for SubmissionCreated { +impl Outbound for SubmissionCreated { + fn id(&self) -> Option { + Some(self.submission_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let profile_id = ctx + .store + .submissions + .by_id(self.submission_id)? + .req()? + .profile_id(); + + remote_inboxes(profile_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let submission = ctx.store.submissions.by_id(self.submission_id)?.req()?; let person_id = ctx.apub.apub_for_profile(submission.profile_id())?.req()?; - let note_id = ctx.apub.info.gen_id(); + let note_id = ctx.apub.info.gen_id().req()?; let note = build_submission(submission, note_id, person_id.clone(), ctx)?; let mut create = Create::new(person_id, note); create - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = create.into_any_base()?; @@ -77,7 +115,22 @@ impl Completed for SubmissionCreated { } } -impl Completed for SubmissionUpdated { +impl Outbound for SubmissionUpdated { + fn id(&self) -> Option { + Some(self.submission_id) + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + let profile_id = ctx + .store + .submissions + .by_id(self.submission_id)? + .req()? + .profile_id(); + + remote_inboxes(profile_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let submission = ctx.store.submissions.by_id(self.submission_id)?.req()?; let person_id = ctx.apub.apub_for_profile(submission.profile_id())?.req()?; @@ -86,7 +139,7 @@ impl Completed for SubmissionUpdated { let mut update = Update::new(person_id, note); update - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = update.into_any_base()?; @@ -96,7 +149,15 @@ impl Completed for SubmissionUpdated { } } -impl Completed for SubmissionDeleted { +impl Outbound for SubmissionDeleted { + fn id(&self) -> Option { + None + } + + fn inboxes(&self, ctx: &Context) -> Result, Error> { + remote_inboxes(self.profile_id, ctx) + } + fn to_apub(&self, ctx: &Context) -> Result { let person_id = ctx.apub.apub_for_profile(self.profile_id)?.req()?; let note_id = ctx.apub.apub_for_submission(self.submission_id)?.req()?; @@ -104,7 +165,7 @@ impl Completed for SubmissionDeleted { let mut delete = Delete::new(person_id, note); delete - .set_id(ctx.apub.info.gen_id()) + .set_id(ctx.apub.info.gen_id().req()?) .add_context(context()) .add_context(security()); let any_base = delete.into_any_base()?; diff --git a/profiles/src/lib.rs b/profiles/src/lib.rs index e7bbd7a..7f5c572 100644 --- a/profiles/src/lib.rs +++ b/profiles/src/lib.rs @@ -44,6 +44,17 @@ impl Context { } } + fn deliver(&self, completed: &dyn Outbound) { + let res = completed + .inboxes(self) + .and_then(|inboxes| completed.to_apub(self).map(|any_base| (inboxes, any_base))); + + match res { + Ok((inboxes, any_base)) => self.spawner.deliver(any_base, inboxes), + Err(e) => log::error!("Error spawning deliver task: {}", e), + } + } + fn is_local(&self, id: Uuid) -> Result { Ok(self.store.profiles.is_local(id)?.req()?) } @@ -100,7 +111,7 @@ pub trait Spawner { fn download_images(&self, images: Vec, stack: Vec); fn purge_file(&self, file_id: Uuid); fn process(&self, any_base: AnyBase, stack: Vec); - fn deliver(&self, completed: &dyn Completed); + fn deliver(&self, any_base: AnyBase, inboxes: Vec); } enum RecoverableError { @@ -108,11 +119,13 @@ enum RecoverableError { MissingImages(Vec), } -trait Action { - fn perform(&self, context: &Context) -> Result>, Error>; +pub trait Action { + fn perform(&self, context: &Context) -> Result>, Error>; } -pub trait Completed { +pub trait Outbound { + fn id(&self) -> Option; + fn inboxes(&self, context: &Context) -> Result, Error>; fn to_apub(&self, context: &Context) -> Result; } @@ -153,6 +166,18 @@ impl State { }) } + pub async fn run(&self, action: &dyn Action) -> Result, Error> { + let ctx = Context::from_state(self).await; + + match action.perform(&ctx)? { + Some(outbound) => { + ctx.deliver(&*outbound); + Ok(outbound.id()) + } + None => Ok(None), + } + } + pub async fn ingest(&self, any_base: AnyBase, stack: Vec) -> Result<(), Error> { let context = Context::from_state(self).await; diff --git a/profiles/src/pictrs.rs b/profiles/src/pictrs.rs index a8c5a08..e1d4c23 100644 --- a/profiles/src/pictrs.rs +++ b/profiles/src/pictrs.rs @@ -1,5 +1,5 @@ use actix_web::{body::BodyStream, client::Client, dev::Payload, HttpRequest, HttpResponse}; -use std::{fmt, sync::Arc}; +use std::{fmt, sync::Arc, time::Duration}; use url::Url; #[derive(Debug, Clone)] @@ -190,7 +190,10 @@ impl State { url: Url, req: &HttpRequest, ) -> Result { - let client_req = self.client.request_from(url.as_str(), req.head()); + let client_req = self + .client + .request_from(url.as_str(), req.head()) + .timeout(Duration::from_secs(30)); let client_req = if let Some(addr) = req.head().peer_addr { client_req.header("X-Forwarded-For", addr.to_string()) } else { @@ -231,7 +234,10 @@ impl State { req: HttpRequest, body: Payload, ) -> Result { - let client_req = self.client.request_from(self.upload().as_str(), req.head()); + let client_req = self + .client + .request_from(self.upload().as_str(), req.head()) + .timeout(Duration::from_secs(30)); let client_req = if let Some(addr) = req.head().peer_addr { client_req.header("X-Forwarded-For", addr.to_string()) @@ -257,6 +263,7 @@ impl State { let mut res = self .client .get(self.download(url).as_str()) + .timeout(Duration::from_secs(30)) .send() .await .map_err(|e| { diff --git a/profiles/src/store/mod.rs b/profiles/src/store/mod.rs index c9672e7..03c6215 100644 --- a/profiles/src/store/mod.rs +++ b/profiles/src/store/mod.rs @@ -85,31 +85,31 @@ impl Profile { &self.owner_source } - pub(crate) fn handle(&self) -> &str { + pub fn handle(&self) -> &str { &self.handle } - pub(crate) fn display_name(&self) -> Option<&str> { + pub fn display_name(&self) -> Option<&str> { self.display_name.as_ref().map(|dn| dn.as_str()) } - pub(crate) fn description(&self) -> Option<&str> { + pub fn description(&self) -> Option<&str> { self.description.as_ref().map(|d| d.as_str()) } - pub(crate) fn icon(&self) -> Option { + pub fn icon(&self) -> Option { self.icon } - pub(crate) fn banner(&self) -> Option { + pub fn banner(&self) -> Option { self.banner } - pub(crate) fn published(&self) -> DateTime { + pub fn published(&self) -> DateTime { self.published } - pub(crate) fn login_required(&self) -> bool { + pub fn login_required(&self) -> bool { self.login_required } } @@ -140,7 +140,7 @@ impl PictRsFile { } } - pub(crate) fn key(&self) -> &str { + pub fn key(&self) -> &str { &self.key } @@ -165,7 +165,7 @@ impl File { self.id } - pub(crate) fn source(&self) -> &FileSource { + pub fn source(&self) -> &FileSource { &self.source } }