diff --git a/crates/apub/src/extensions/group_extensions.rs b/crates/apub/src/extensions/group_extensions.rs index 11ea83822..c83becf21 100644 --- a/crates/apub/src/extensions/group_extensions.rs +++ b/crates/apub/src/extensions/group_extensions.rs @@ -1,7 +1,4 @@ -use activitystreams::{ - collection::{CollectionExt, OrderedCollection}, - unparsed::UnparsedMutExt, -}; +use activitystreams::unparsed::UnparsedMutExt; use activitystreams_ext::UnparsedExtension; use lemmy_utils::LemmyError; use serde::{Deserialize, Serialize}; @@ -13,17 +10,14 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct GroupExtension { pub sensitive: Option, - pub moderators: Option, + pub moderators: Option, } impl GroupExtension { - pub fn new(sensitive: bool, moderators: Vec) -> Result { - let mut mods = OrderedCollection::new(); - mods.set_total_items(moderators.len() as u64); - mods.set_many_items(moderators); + pub fn new(sensitive: bool, moderators_url: Url) -> Result { Ok(GroupExtension { sensitive: Some(sensitive), - moderators: Some(mods), + moderators: Some(moderators_url), }) } } diff --git a/crates/apub/src/fetcher/community.rs b/crates/apub/src/fetcher/community.rs index b2440bf2b..9cc1bbd6a 100644 --- a/crates/apub/src/fetcher/community.rs +++ b/crates/apub/src/fetcher/community.rs @@ -103,3 +103,27 @@ async fn fetch_community_outbox( Ok(()) } + +pub(crate) async fn fetch_community_mods( + context: &LemmyContext, + group: &GroupExt, + recursion_counter: &mut i32, +) -> Result, LemmyError> { + if let Some(mods_url) = &group.ext_one.moderators { + let mods = + fetch_remote_object::(context.client(), mods_url, recursion_counter) + .await?; + let mods = mods + .items() + .map(|i| i.as_many()) + .flatten() + .context(location_info!())? + .iter() + .filter_map(|i| i.as_xsd_any_uri()) + .map(|u| u.to_owned()) + .collect(); + Ok(mods) + } else { + Ok(vec![]) + } +} diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index d4287224c..ee777fff9 100644 --- a/crates/apub/src/http/comment.rs +++ b/crates/apub/src/http/comment.rs @@ -12,12 +12,12 @@ use lemmy_websocket::LemmyContext; use serde::Deserialize; #[derive(Deserialize)] -pub struct CommentQuery { +pub(crate) struct CommentQuery { comment_id: String, } /// Return the ActivityPub json representation of a local comment over HTTP. -pub async fn get_apub_comment( +pub(crate) async fn get_apub_comment( info: Path, context: web::Data, ) -> Result, LemmyError> { diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 2306286a7..fcf20748a 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -1,5 +1,6 @@ use crate::{ extensions::context::lemmy_context, + generate_moderators_url, http::{create_apub_response, create_apub_tombstone_response}, objects::ToApub, ActorType, @@ -7,23 +8,27 @@ use crate::{ use activitystreams::{ base::{AnyBase, BaseExt}, collection::{CollectionExt, OrderedCollection, UnorderedCollection}, + url::Url, }; use actix_web::{body::Body, web, HttpResponse}; use lemmy_api_structs::blocking; use lemmy_db_queries::source::{activity::Activity_, community::Community_}; use lemmy_db_schema::source::{activity::Activity, community::Community}; -use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; +use lemmy_db_views_actor::{ + community_follower_view::CommunityFollowerView, + community_moderator_view::CommunityModeratorView, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::Deserialize; #[derive(Deserialize)] -pub struct CommunityQuery { +pub(crate) struct CommunityQuery { community_name: String, } /// Return the ActivityPub json representation of a local community over HTTP. -pub async fn get_apub_community_http( +pub(crate) async fn get_apub_community_http( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -42,7 +47,7 @@ pub async fn get_apub_community_http( } /// Returns an empty followers collection, only populating the size (for privacy). -pub async fn get_apub_community_followers( +pub(crate) async fn get_apub_community_followers( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -67,7 +72,7 @@ pub async fn get_apub_community_followers( /// Returns the community outbox, which is populated by a maximum of 20 posts (but no other /// activites like votes or comments). -pub async fn get_apub_community_outbox( +pub(crate) async fn get_apub_community_outbox( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -96,7 +101,7 @@ pub async fn get_apub_community_outbox( Ok(create_apub_response(&collection)) } -pub async fn get_apub_community_inbox( +pub(crate) async fn get_apub_community_inbox( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -107,7 +112,39 @@ pub async fn get_apub_community_inbox( let mut collection = OrderedCollection::new(); collection - .set_id(format!("{}/inbox", community.actor_id).parse()?) + .set_id(community.inbox_url.into()) + .set_many_contexts(lemmy_context()?); + Ok(create_apub_response(&collection)) +} + +pub(crate) async fn get_apub_community_moderators( + info: web::Path, + context: web::Data, +) -> Result, LemmyError> { + let community = blocking(context.pool(), move |conn| { + Community::read_from_name(&conn, &info.community_name) + }) + .await??; + + // 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 cid = community.id; + let moderators = blocking(context.pool(), move |conn| { + CommunityModeratorView::for_community(&conn, cid) + }) + .await??; + + let moderators: Vec = moderators + .into_iter() + .map(|m| m.moderator.actor_id.into_inner()) + .collect(); + let mut collection = OrderedCollection::new(); + collection + .set_id(generate_moderators_url(&community.actor_id)?.into()) + .set_total_items(moderators.len() as u64) + .set_many_items(moderators) .set_many_contexts(lemmy_context()?); Ok(create_apub_response(&collection)) } diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 6bf4bbde6..b343a6e8d 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -42,7 +42,7 @@ pub struct CommunityQuery { } /// Return the ActivityPub json representation of a local community over HTTP. -pub async fn get_activity( +pub(crate) async fn get_activity( info: web::Path, context: web::Data, ) -> Result, LemmyError> { diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs index 8bdded2a0..1b5589a0f 100644 --- a/crates/apub/src/http/post.rs +++ b/crates/apub/src/http/post.rs @@ -12,12 +12,12 @@ use lemmy_websocket::LemmyContext; use serde::Deserialize; #[derive(Deserialize)] -pub struct PostQuery { +pub(crate) struct PostQuery { post_id: String, } /// Return the ActivityPub json representation of a local post over HTTP. -pub async fn get_apub_post( +pub(crate) async fn get_apub_post( info: web::Path, context: web::Data, ) -> Result, LemmyError> { diff --git a/crates/apub/src/http/user.rs b/crates/apub/src/http/user.rs index 77c40d855..dcb73e3b0 100644 --- a/crates/apub/src/http/user.rs +++ b/crates/apub/src/http/user.rs @@ -18,12 +18,12 @@ use serde::Deserialize; use url::Url; #[derive(Deserialize)] -pub struct UserQuery { +pub(crate) struct UserQuery { user_name: String, } /// Return the ActivityPub json representation of a local user over HTTP. -pub async fn get_apub_user_http( +pub(crate) async fn get_apub_user_http( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -43,7 +43,7 @@ pub async fn get_apub_user_http( } } -pub async fn get_apub_user_outbox( +pub(crate) async fn get_apub_user_outbox( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -61,7 +61,7 @@ pub async fn get_apub_user_outbox( Ok(create_apub_response(&collection)) } -pub async fn get_apub_user_inbox( +pub(crate) async fn get_apub_user_inbox( info: web::Path, context: web::Data, ) -> Result, LemmyError> { @@ -72,7 +72,7 @@ pub async fn get_apub_user_inbox( let mut collection = OrderedCollection::new(); collection - .set_id(format!("{}/inbox", user.actor_id.into_inner()).parse()?) + .set_id(user.inbox_url.into()) .set_many_contexts(lemmy_context()?); Ok(create_apub_response(&collection)) } diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 850ef503e..307a8c8c0 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -262,6 +262,10 @@ pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result Ok(Url::parse(&url)?.into()) } +pub(crate) fn generate_moderators_url(community_id: &DbUrl) -> Result { + Ok(Url::parse(&format!("{}/moderators", community_id))?.into()) +} + /// Store a sent or received activity in the database, for logging purposes. These records are not /// persistent. pub(crate) async fn insert_activity( diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 9431f71cd..278bd7b12 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -1,6 +1,7 @@ use crate::{ extensions::{context::lemmy_context, group_extensions::GroupExtension}, - fetcher::user::get_or_fetch_and_upsert_user, + fetcher::{community::fetch_community_mods, user::get_or_fetch_and_upsert_user}, + generate_moderators_url, objects::{ check_object_domain, create_tombstone, @@ -42,17 +43,7 @@ use url::Url; impl ToApub for Community { type ApubType = GroupExt; - async fn to_apub(&self, pool: &DbPool) -> Result { - // 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 id = self.id; - let moderators = blocking(pool, move |conn| { - CommunityModeratorView::for_community(&conn, id) - }) - .await??; - + async fn to_apub(&self, _pool: &DbPool) -> Result { let mut group = ApObject::new(Group::new()); group .set_many_contexts(lemmy_context()?) @@ -89,14 +80,9 @@ impl ToApub for Community { ..Default::default() }); - let moderators: Vec = moderators - .into_iter() - .map(|m| m.moderator.actor_id.into_inner()) - .collect(); - Ok(Ext2::new( ap_actor, - GroupExtension::new(self.nsfw, moderators)?, + GroupExtension::new(self.nsfw, generate_moderators_url(&self.actor_id)?.into())?, self.get_public_key_ext()?, )) } @@ -125,7 +111,7 @@ impl FromApub for Community { let community: Community = get_object_from_apub(group, context, expected_domain, request_counter).await?; - let new_moderators = get_community_moderators(group)?; + let new_moderators = fetch_community_mods(context, group, request_counter).await?; let community_id = community.id; let current_moderators = blocking(context.pool(), move |conn| { CommunityModeratorView::for_community(&conn, community_id) @@ -177,7 +163,7 @@ impl FromApubToForm for CommunityForm { expected_domain: Url, request_counter: &mut i32, ) -> Result { - let moderator_uris = get_community_moderators(group)?; + let moderator_uris = fetch_community_mods(context, group, request_counter).await?; let creator_uri = moderator_uris.first().context(location_info!())?; let creator = get_or_fetch_and_upsert_user(creator_uri, context, request_counter).await?; @@ -263,20 +249,3 @@ impl FromApubToForm for CommunityForm { }) } } - -fn get_community_moderators(group: &GroupExt) -> Result, LemmyError> { - if let Some(moderators) = &group.ext_one.moderators { - Ok( - moderators - .items() - .map(|i| i.as_many()) - .flatten() - .context(location_info!())? - .iter() - .filter_map(|i| i.as_xsd_any_uri()) - .collect(), - ) - } else { - Ok(vec![]) - } -} diff --git a/crates/apub/src/routes.rs b/crates/apub/src/routes.rs index 07dcc7f88..2ec8c26d0 100644 --- a/crates/apub/src/routes.rs +++ b/crates/apub/src/routes.rs @@ -5,6 +5,7 @@ use crate::{ get_apub_community_followers, get_apub_community_http, get_apub_community_inbox, + get_apub_community_moderators, get_apub_community_outbox, }, get_activity, @@ -53,6 +54,10 @@ pub fn config(cfg: &mut web::ServiceConfig) { "/c/{community_name}/inbox", web::get().to(get_apub_community_inbox), ) + .route( + "/c/{community_name}/moderators", + web::get().to(get_apub_community_moderators), + ) .route("/u/{user_name}", web::get().to(get_apub_user_http)) .route("/u/{user_name}/outbox", web::get().to(get_apub_user_outbox)) .route("/u/{user_name}/inbox", web::get().to(get_apub_user_inbox))