diff --git a/api_tests/src/private_community.spec.ts b/api_tests/src/private_community.spec.ts index e467344c3..dc2c4fe35 100644 --- a/api_tests/src/private_community.spec.ts +++ b/api_tests/src/private_community.spec.ts @@ -298,6 +298,20 @@ test.only("Fetch remote content in private community", async () => { const comment_id = comment.comment_view.comment.id; expect(comment_id).toBeDefined(); + // Wait for post and comment to federate + /* + console.log('a'); + await waitUntil( + () => resolvePost(alpha, post.post_view.post), + p => p?.post?.post.id != undefined, + ); + await waitUntil( + () => resolveComment(gamma, comment.comment_view.comment), + p => p?.post?.post.id != undefined, + ); + */ + console.log("b"); + // create gamma user and follow community const gammaCommunityId = ( await resolveCommunity(gamma, community.community_view.community.actor_id) @@ -310,23 +324,22 @@ test.only("Fetch remote content in private community", async () => { await approveFollower(alpha, alphaCommunityId); // now user can fetch posts and comments in community (using signed fetch), and create posts + // TODO: this fails because beta doesnt know if the gamma user was approved by alpha community + console.log(0); let resolvedPost = await waitUntil( () => resolvePost(gamma, post.post_view.post), p => p?.post?.post.id != undefined, ); - console.log(post.post_view.post); - console.log(resolvedPost.post?.post); expect(resolvedPost.post?.post.ap_id).toBe(post.post_view.post.ap_id); - const resolvedComment = ( - await resolveComment(gamma, comment.comment_view.comment) - ).comment; - expect(resolvedComment?.comment.ap_id).toBe( + console.log(1); + const resolvedComment = await waitUntil( + () => resolveComment(gamma, comment.comment_view.comment), + p => p?.post?.post.id != undefined, + ); + expect(resolvedComment?.comment?.comment.ap_id).toBe( comment.comment_view.comment.ap_id, ); - - // TODO: this test should fail as check_has_followers_from_instance() on beta returns errors - // because it doesnt know the community follower. yet for some reason the test passes??? - fail(); + console.log(2); }); async function approveFollower(user: LemmyHttp, community_id: number) { diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 96a917d91..246318d8d 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -6,16 +6,19 @@ use crate::{ community_moderators::ApubCommunityModerators, community_outbox::ApubCommunityOutbox, }, + fetcher::site_or_community_or_user::SiteOrCommunityOrUser, http::{check_community_fetchable, create_apub_response, create_apub_tombstone_response}, objects::community::ApubCommunity, }; use activitypub_federation::{ config::Data, + fetch::object_id::ObjectId, traits::{Collection, Object}, }; use actix_web::{web, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::ApubActor}; +use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::Deserialize; @@ -24,6 +27,11 @@ pub(crate) struct CommunityQuery { community_name: String, } +#[derive(Deserialize)] +pub struct CommunityIsFollowerQuery { + is_follower: Option>, +} + /// Return the ActivityPub json representation of a local community over HTTP. #[tracing::instrument(skip_all)] pub(crate) async fn get_apub_community_http( @@ -48,11 +56,26 @@ pub(crate) async fn get_apub_community_http( /// Returns an empty followers collection, only populating the size (for privacy). pub(crate) async fn get_apub_community_followers( info: web::Path, + query: web::Query, context: Data, ) -> LemmyResult { let community = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? .ok_or(LemmyErrorType::NotFound)?; + if let Some(is_follower) = &query.is_follower { + let instance_id = is_follower.dereference(&context).await?.instance_id(); + let has_followers = CommunityFollowerView::check_has_followers_from_instance( + community.id, + instance_id, + &mut context.pool(), + ) + .await; + return if has_followers.is_ok() { + Ok(HttpResponse::Ok().finish()) + } else { + Ok(HttpResponse::NotFound().finish()) + }; + } check_community_fetchable(&community)?; let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?; create_apub_response(&followers) diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 8780f8789..9dc970240 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -8,6 +8,7 @@ use activitypub_federation::{ actix_web::{inbox::receive_activity, signing_actor}, config::Data, protocol::context::WithContext, + traits::Actor, FEDERATION_CONTENT_TYPE, }; use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; @@ -145,14 +146,30 @@ async fn check_community_content_fetchable( // from the fetching instance then fetching is allowed Private => { let signing_actor = signing_actor::(request, None, context).await?; - Ok( - CommunityFollowerView::check_has_followers_from_instance( - community.id, - signing_actor.instance_id(), - &mut context.pool(), + if community.local { + Ok( + CommunityFollowerView::check_has_followers_from_instance( + community.id, + signing_actor.instance_id(), + &mut context.pool(), + ) + .await?, ) - .await?, - ) + } else if let Some(followers_url) = community.followers_url.clone() { + let mut followers_url = followers_url.inner().clone(); + followers_url + .query_pairs_mut() + .append_pair("is_follower", signing_actor.id().as_str()); + context + .client() + .get(followers_url.as_str()) + .send() + .await? + .error_for_status()?; + Ok(()) + } else { + Err(LemmyErrorType::NotFound.into()) + } } } } diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index 56f4cca93..c32ccb5b8 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -232,6 +232,25 @@ impl CommunityFollowerView { .then_some(()) .ok_or(diesel::NotFound) } + + pub async fn is_follower( + community_id: CommunityId, + instance_id: InstanceId, + pool: &mut DbPool<'_>, + ) -> Result<(), Error> { + let conn = &mut get_conn(pool).await?; + select(exists( + action_query(community_actions::followed) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + .filter(community_actions::community_id.eq(community_id)) + .filter(person::instance_id.eq(instance_id)) + .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + )) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(diesel::NotFound) + } } #[cfg(test)]