Skip to content

Commit

Permalink
Local only community (#4350)
Browse files Browse the repository at this point in the history
* Add support for local only community (fixes #1576)

* add filters and tests to db views

* dont federate local only community

* test get apub community http

* tests

* more checks

* wip

* api test

* fix tests

* change community.local_only column to visibility enum
(for private communities)

* sql fmt

* rename vars

* clippy

* fix tests

* update lib

* review

* fix js client version

* update client
  • Loading branch information
Nutomic authored Jan 25, 2024
1 parent 8cde452 commit 0f414a9
Show file tree
Hide file tree
Showing 37 changed files with 963 additions and 536 deletions.
2 changes: 1 addition & 1 deletion api_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"eslint": "^8.55.0",
"eslint-plugin-prettier": "^5.0.1",
"jest": "^29.5.0",
"lemmy-js-client": "0.19.2-alpha.2",
"lemmy-js-client": "0.19.3-alpha.2",
"prettier": "^3.1.1",
"ts-jest": "^29.1.0",
"typescript": "^5.3.3"
Expand Down
24 changes: 23 additions & 1 deletion api_tests/src/community.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import {
resolveBetaCommunity,
longDelay,
delay,
editCommunity,
} from "./shared";
import { EditSite } from "lemmy-js-client";
import { EditCommunity, EditSite } from "lemmy-js-client";

beforeAll(setupLogins);

Expand Down Expand Up @@ -511,3 +512,24 @@ test("Fetch community, includes posts", async () => {
expect(post_listing.posts.length).toBe(1);
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
});

test("Content in local-only community doesnt federate", async () => {
// create a community and set it local-only
let communityRes = (await createCommunity(alpha)).community_view.community;
let form: EditCommunity = {
community_id: communityRes.id,
visibility: "LocalOnly",
};
await editCommunity(alpha, form);

// cant resolve the community from another instance
await expect(
resolveCommunity(beta, communityRes.actor_id),
).rejects.toStrictEqual(Error("couldnt_find_object"));

// create a post, also cant resolve it
let postRes = await createPost(alpha, communityRes.id);
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
Error("couldnt_find_object"),
);
});
8 changes: 8 additions & 0 deletions api_tests/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BlockInstanceResponse,
CommunityId,
CreatePrivateMessageReport,
EditCommunity,
GetReplies,
GetRepliesResponse,
GetUnreadCountResponse,
Expand Down Expand Up @@ -532,6 +533,13 @@ export async function createCommunity(
return api.createCommunity(form);
}

export async function editCommunity(
api: LemmyHttp,
form: EditCommunity,
): Promise<CommunityResponse> {
return api.editCommunity(form);
}

export async function getCommunity(
api: LemmyHttp,
id: number,
Expand Down
773 changes: 312 additions & 461 deletions api_tests/yarn.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/api_common/src/community.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use lemmy_db_schema::{
newtypes::{CommunityId, LanguageId, PersonId},
source::site::Site,
CommunityVisibility,
ListingType,
SortType,
};
Expand Down Expand Up @@ -54,6 +55,7 @@ pub struct CreateCommunity {
/// Whether to restrict posting only to moderators.
pub posting_restricted_to_mods: Option<bool>,
pub discussion_languages: Option<Vec<LanguageId>>,
pub visibility: Option<CommunityVisibility>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -150,6 +152,7 @@ pub struct EditCommunity {
/// Whether to restrict posting only to moderators.
pub posting_restricted_to_mods: Option<bool>,
pub discussion_languages: Option<Vec<LanguageId>>,
pub visibility: Option<CommunityVisibility>,
}

#[skip_serializing_none]
Expand Down
1 change: 1 addition & 0 deletions crates/api_crud/src/community/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub async fn create_community(
.shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?))
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.instance_id(site_view.site.instance_id)
.visibility(data.visibility)
.build();

let inserted_community = Community::create(&mut context.pool(), &community_form)
Expand Down
1 change: 1 addition & 0 deletions crates/api_crud/src/community/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub async fn update_community(
banner,
nsfw: data.nsfw,
posting_restricted_to_mods: data.posting_restricted_to_mods,
visibility: data.visibility,
updated: Some(Some(naive_now())),
..Default::default()
};
Expand Down
31 changes: 17 additions & 14 deletions crates/api_crud/src/post/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use lemmy_db_schema::{
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
},
traits::{Crud, Likeable},
CommunityVisibility,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView;
Expand Down Expand Up @@ -165,20 +166,22 @@ pub async fn create_post(
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

if let Some(url) = updated_post.url.clone() {
spawn_try_task(async move {
let mut webmention =
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
webmention.set_checked(true);
match webmention
.send()
.instrument(tracing::info_span!("Sending webmention"))
.await
{
Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
Ok(_) => Ok(()),
Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
}
});
if community.visibility == CommunityVisibility::Public {
spawn_try_task(async move {
let mut webmention =
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
webmention.set_checked(true);
match webmention
.send()
.instrument(tracing::info_span!("Sending webmention"))
.await
{
Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
Ok(_) => Ok(()),
Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
}
});
}
};

build_post_response(&context, community_id, &local_user_view.person, post_id).await
Expand Down
2 changes: 1 addition & 1 deletion crates/api_crud/src/site/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub async fn get_site(
|pool| CommunityBlockView::for_person(pool, person_id),
|pool| InstanceBlockView::for_person(pool, person_id),
|pool| PersonBlockView::for_person(pool, person_id),
|pool| CommunityModeratorView::for_person(pool, person_id),
|pool| CommunityModeratorView::for_person(pool, person_id, true),
|pool| LocalUserLanguage::read(pool, local_user_id)
))
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
Expand Down
9 changes: 8 additions & 1 deletion crates/apub/src/activities/community/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::{activity::ActivitySendTargets, community::CommunityFollower};
use lemmy_db_schema::{
source::{activity::ActivitySendTargets, community::CommunityFollower},
CommunityVisibility,
};
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
use serde_json::Value;
use url::Url;
Expand Down Expand Up @@ -210,6 +213,10 @@ async fn can_accept_activity_in_community(
{
Err(LemmyErrorType::CommunityHasNoFollowers)?
}
// Local only community can't federate
if community.visibility != CommunityVisibility::Public {
return Err(LemmyErrorType::CouldntFindCommunity.into());
}
}
Ok(())
}
10 changes: 9 additions & 1 deletion crates/apub/src/activities/community/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use crate::{
};
use activitypub_federation::{config::Data, traits::Actor};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::{activity::ActivitySendTargets, person::PersonFollower};
use lemmy_db_schema::{
source::{activity::ActivitySendTargets, person::PersonFollower},
CommunityVisibility,
};
use lemmy_utils::error::LemmyError;

pub mod announce;
Expand Down Expand Up @@ -37,6 +40,11 @@ pub(crate) async fn send_activity_in_community(
is_mod_action: bool,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
// If community is local only, don't send anything out
if community.visibility != CommunityVisibility::Public {
return Ok(());
}

// send to any users which are mentioned or affected directly
let mut inboxes = extra_inboxes;

Expand Down
6 changes: 1 addition & 5 deletions crates/apub/src/activities/community/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,10 @@ impl ActivityHandler for UpdateCommunity {
&None,
&self.object.source,
)),
removed: None,
published: self.object.published.map(Into::into),
updated: Some(self.object.updated.map(Into::into)),
deleted: None,
nsfw: Some(self.object.sensitive.unwrap_or(false)),
actor_id: Some(self.object.id.into()),
local: None,
private_key: None,
hidden: None,
public_key: Some(self.object.public_key.public_key_pem),
last_refreshed_at: Some(naive_now()),
icon: Some(self.object.icon.map(|i| i.url.into())),
Expand All @@ -116,6 +111,7 @@ impl ActivityHandler for UpdateCommunity {
moderators_url: self.object.attributed_to.map(Into::into),
posting_restricted_to_mods: self.object.posting_restricted_to_mods,
featured_url: self.object.featured.map(Into::into),
..Default::default()
};

Community::update(&mut context.pool(), community.id, &community_update_form).await?;
Expand Down
7 changes: 6 additions & 1 deletion crates/apub/src/activities/following/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ use lemmy_db_schema::{
person::{PersonFollower, PersonFollowerForm},
},
traits::Followable,
CommunityVisibility,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url;

impl Follow {
Expand Down Expand Up @@ -103,6 +104,10 @@ impl ActivityHandler for Follow {
PersonFollower::follow(&mut context.pool(), &form).await?;
}
UserOrCommunity::Community(c) => {
// Dont allow following local-only community via federation.
if c.visibility != CommunityVisibility::Public {
return Err(LemmyErrorType::CouldntFindCommunity.into());
}
let form = CommunityFollowerForm {
community_id: c.id,
person_id: actor.id,
Expand Down
8 changes: 6 additions & 2 deletions crates/apub/src/api/read_person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@ pub async fn read_person(
.list(&mut context.pool())
.await?;

let moderates =
CommunityModeratorView::for_person(&mut context.pool(), person_details_id).await?;
let moderates = CommunityModeratorView::for_person(
&mut context.pool(),
person_details_id,
local_user_view.is_some(),
)
.await?;

let site = read_site_for_actor(person_view.person.actor_id.clone(), &context).await?;

Expand Down
2 changes: 1 addition & 1 deletion crates/apub/src/collections/community_featured.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use lemmy_db_schema::{source::post::Post, utils::FETCH_LIMIT_MAX};
use lemmy_utils::error::LemmyError;
use url::Url;

#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ApubCommunityFeatured(());

#[async_trait::async_trait]
Expand Down
18 changes: 16 additions & 2 deletions crates/apub/src/http/comment.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use crate::{
http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object},
http::{
check_community_public,
create_apub_response,
create_apub_tombstone_response,
redirect_remote_object,
},
objects::comment::ApubComment,
};
use activitypub_federation::{config::Data, traits::Object};
use actix_web::{web::Path, HttpResponse};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{newtypes::CommentId, source::comment::Comment, traits::Crud};
use lemmy_db_schema::{
newtypes::CommentId,
source::{comment::Comment, community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::error::LemmyError;
use serde::Deserialize;

Expand All @@ -21,7 +30,12 @@ pub(crate) async fn get_apub_comment(
context: Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let id = CommentId(info.comment_id.parse::<i32>()?);
// Can't use CommentView here because it excludes deleted/removed/local-only items
let comment: ApubComment = Comment::read(&mut context.pool(), id).await?.into();
let post = Post::read(&mut context.pool(), comment.post_id).await?;
let community = Community::read(&mut context.pool(), post.community_id).await?;
check_community_public(&community)?;

if !comment.local {
Ok(redirect_remote_object(&comment.ap_id))
} else if !comment.deleted && !comment.removed {
Expand Down
Loading

0 comments on commit 0f414a9

Please sign in to comment.