Implement separate mod activities for feature, lock post (#2716)
* Implement separate mod activities for feature, lock post Also includes collection for featured posts. Later we also need to do the same for Comment.distinguished * some changes --------- Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
8409e50f8c
commit
62663a9f2e
54 changed files with 1294 additions and 679 deletions
|
@ -840,6 +840,10 @@ pub fn generate_outbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
|||
Ok(Url::parse(&format!("{actor_id}/outbox"))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_featured_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
||||
Ok(Url::parse(&format!("{actor_id}/featured"))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
|
||||
Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"cc": [
|
||||
"https://ds9.lemmy.ml/c/main"
|
||||
],
|
||||
"id": "https://ds9.lemmy.ml/activities/add/47d911f5-52c5-4659-b2fd-0e58c451a427",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Add",
|
||||
"actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
|
||||
"object": "https://ds9.lemmy.ml/post/2",
|
||||
"target": "https://ds9.lemmy.ml/c/main/featured",
|
||||
"audience": "https://ds9.lemmy.ml/c/main"
|
||||
}
|
13
crates/apub/assets/lemmy/activities/community/lock_page.json
Normal file
13
crates/apub/assets/lemmy/activities/community/lock_page.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "http://lemmy-alpha:8541/activities/lock/cb48761d-9e8c-42ce-aacb-b4bbe6408db2",
|
||||
"actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"object": "http://lemmy-alpha:8541/post/2",
|
||||
"cc": [
|
||||
"http://lemmy-alpha:8541/c/main"
|
||||
],
|
||||
"type": "Lock",
|
||||
"audience": "http://lemmy-alpha:8541/c/main"
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"cc": [
|
||||
"https://ds9.lemmy.ml/c/main"
|
||||
],
|
||||
"id": "https://ds9.lemmy.ml/activities/add/47d911f5-52c5-4659-b2fd-0e58c451a427",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Remove",
|
||||
"actor": "https://ds9.lemmy.ml/u/lemmy_alpha",
|
||||
"object": "https://ds9.lemmy.ml/post/2",
|
||||
"target": "https://ds9.lemmy.ml/c/main/featured",
|
||||
"audience": "https://ds9.lemmy.ml/c/main"
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"id": "http://lemmy-alpha:8541/activities/undo/d6066719-d277-4964-9190-4d6faffac286",
|
||||
"actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"object": {
|
||||
"actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"object": "http://lemmy-alpha:8541/post/2",
|
||||
"cc": [
|
||||
"http://lemmy-alpha:8541/c/main"
|
||||
],
|
||||
"type": "Lock",
|
||||
"id": "http://lemmy-alpha:8541/activities/lock/08b6fd3e-9ef3-4358-a987-8bb641f3e2c3",
|
||||
"audience": "http://lemmy-alpha:8541/c/main"
|
||||
},
|
||||
"cc": [
|
||||
"http://lemmy-alpha:8541/c/main"
|
||||
],
|
||||
"type": "Undo",
|
||||
"audience": "http://lemmy-alpha:8541/c/main"
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"type": "OrderedCollection",
|
||||
"id": "https://ds9.lemmy.ml/c/main/featured",
|
||||
"totalItems": 2,
|
||||
"orderedItems": [
|
||||
{
|
||||
"type": "Page",
|
||||
"id": "https://ds9.lemmy.ml/post/2",
|
||||
"attributedTo": "https://ds9.lemmy.ml/u/lemmy_alpha",
|
||||
"to": [
|
||||
"https://ds9.lemmy.ml/c/main",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"name": "test 2",
|
||||
"cc": [],
|
||||
"mediaType": "text/html",
|
||||
"attachment": [],
|
||||
"commentsEnabled": true,
|
||||
"sensitive": false,
|
||||
"stickied": true,
|
||||
"published": "2023-02-06T06:42:41.939437+00:00",
|
||||
"language": {
|
||||
"identifier": "de",
|
||||
"name": "Deutsch"
|
||||
},
|
||||
"audience": "https://ds9.lemmy.ml/c/main"
|
||||
},
|
||||
{
|
||||
"type": "Page",
|
||||
"id": "https://ds9.lemmy.ml/post/1",
|
||||
"attributedTo": "https://ds9.lemmy.ml/u/lemmy_alpha",
|
||||
"to": [
|
||||
"https://ds9.lemmy.ml/c/main",
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"name": "test 1",
|
||||
"cc": [],
|
||||
"mediaType": "text/html",
|
||||
"attachment": [],
|
||||
"commentsEnabled": true,
|
||||
"sensitive": false,
|
||||
"stickied": true,
|
||||
"published": "2023-02-06T06:42:37.119567+00:00",
|
||||
"language": {
|
||||
"identifier": "de",
|
||||
"name": "Deutsch"
|
||||
},
|
||||
"audience": "https://ds9.lemmy.ml/c/main"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
"followers": "https://enterprise.lemmy.ml/c/tenforward/followers",
|
||||
"moderators": "https://enterprise.lemmy.ml/c/tenforward/moderators",
|
||||
"attributedTo": "https://enterprise.lemmy.ml/c/tenforward/moderators",
|
||||
"featured": "https://enterprise.lemmy.ml/c/tenforward//featured",
|
||||
"postingRestrictedToMods": false,
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://enterprise.lemmy.ml/inbox"
|
||||
|
|
73
crates/apub/assets/mastodon/collections/featured.json
Normal file
73
crates/apub/assets/mastodon/collections/featured.json
Normal file
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"conversation": "ostatus:conversation",
|
||||
"sensitive": "as:sensitive",
|
||||
"toot": "http://joinmastodon.org/ns#",
|
||||
"votersCount": "toot:votersCount",
|
||||
"Hashtag": "as:Hashtag"
|
||||
}
|
||||
],
|
||||
"id": "https://mastodon.social/users/LemmyDev/collections/featured",
|
||||
"type": "OrderedCollection",
|
||||
"totalItems": 1,
|
||||
"orderedItems": [
|
||||
{
|
||||
"id": "https://mastodon.social/users/LemmyDev/statuses/104246642906910728",
|
||||
"type": "Note",
|
||||
"summary": null,
|
||||
"inReplyTo": null,
|
||||
"published": "2020-05-28T14:52:14Z",
|
||||
"url": "https://mastodon.social/@LemmyDev/104246642906910728",
|
||||
"attributedTo": "https://mastodon.social/users/LemmyDev",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://mastodon.social/users/LemmyDev/followers"
|
||||
],
|
||||
"sensitive": false,
|
||||
"atomUri": "https://mastodon.social/users/LemmyDev/statuses/104246642906910728",
|
||||
"inReplyToAtomUri": null,
|
||||
"conversation": "tag:mastodon.social,2020-05-28:objectId=175451535:objectType=Conversation",
|
||||
"content": "<p>Inaugural Post for Lemmy, a decentralized, easily self-hostable <a href=\"https://mastodon.social/tags/reddit\" class=\"mention hashtag\" rel=\"tag\">#<span>reddit</span></a> / link aggregator alternative,intended to work in the <a href=\"https://mastodon.social/tags/fediverse\" class=\"mention hashtag\" rel=\"tag\">#<span>fediverse</span></a>: </p><p><a href=\"https://github.com/LemmyNet/lemmy/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><span class=\"invisible\">https://</span><span class=\"\">github.com/LemmyNet/lemmy/</span><span class=\"invisible\"></span></a></p><p><a href=\"https://mastodon.social/tags/activitypub\" class=\"mention hashtag\" rel=\"tag\">#<span>activitypub</span></a></p>",
|
||||
"contentMap": {
|
||||
"en": "<p>Inaugural Post for Lemmy, a decentralized, easily self-hostable <a href=\"https://mastodon.social/tags/reddit\" class=\"mention hashtag\" rel=\"tag\">#<span>reddit</span></a> / link aggregator alternative, intended to work in the <a href=\"https://mastodon.social/tags/fediverse\" class=\"mention hashtag\" rel=\"tag\">#<span>fediverse</span></a>: </p><p><a href=\"https://github.com/LemmyNet/lemmy/\" target=\"_blank\" rel=\"nofollownoopener noreferrer\"><span class=\"invisible\">https://</span><span class=\"\">github.com/LemmyNet/lemmy/</span><span class=\"invisible\"></span></a></p><p><a href=\"https://mastodon.social/tags/activitypub\" class=\"mentionhashtag\" rel=\"tag\">#<span>activitypub</span></a></p>"
|
||||
},
|
||||
"attachment": [],
|
||||
"tag": [
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"href": "https://mastodon.social/tags/reddit",
|
||||
"name": "#reddit"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"href": "https://mastodon.social/tags/fediverse",
|
||||
"name": "#fediverse"
|
||||
},
|
||||
{
|
||||
"type": "Hashtag",
|
||||
"href": "https://mastodon.social/tags/activitypub",
|
||||
"name": "#activitypub"
|
||||
}
|
||||
],
|
||||
"replies": {
|
||||
"id": "https://mastodon.social/users/LemmyDev/statuses/104246642906910728/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"type": "CollectionPage",
|
||||
"next": "https://mastodon.social/users/LemmyDev/statuses/104246642906910728/replies?min_id=104246644059085152&page=true",
|
||||
"partOf": "https://mastodon.social/users/LemmyDev/statuses/104246642906910728/replies",
|
||||
"items": [
|
||||
"https://mastodon.social/users/LemmyDev/statuses/104246644059085152"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::send_activity_in_community,
|
||||
generate_activity_id,
|
||||
verify_add_remove_moderator_target,
|
||||
verify_is_public,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{
|
||||
activities::community::{add_mod::AddMod, remove_mod::RemoveMod},
|
||||
InCommunity,
|
||||
},
|
||||
ActorType,
|
||||
SendActivity,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
core::object_id::ObjectId,
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use activitystreams_kinds::{activity::AddType, public};
|
||||
use lemmy_api_common::{
|
||||
community::{AddModToCommunity, AddModToCommunityResponse},
|
||||
context::LemmyContext,
|
||||
utils::{generate_moderators_url, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
impl AddMod {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send(
|
||||
community: &ApubCommunity,
|
||||
added_mod: &ApubPerson,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
AddType::Add,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let add = AddMod {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: ObjectId::new(added_mod.actor_id()),
|
||||
target: generate_moderators_url(&community.actor_id)?.into(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: AddType::Add,
|
||||
id: id.clone(),
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::AddMod(add);
|
||||
let inboxes = vec![added_mod.shared_inbox_or_inbox()];
|
||||
send_activity_in_community(activity, actor, community, inboxes, true, context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for AddMod {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
self.object.inner(),
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
verify_add_remove_moderator_target(&self.target, &community)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = self.community(context, request_counter).await?;
|
||||
let new_mod = self
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
|
||||
// If we had to refetch the community while parsing the activity, then the new mod has already
|
||||
// been added. Skip it here as it would result in a duplicate key error.
|
||||
let new_mod_id = new_mod.id;
|
||||
let moderated_communities =
|
||||
CommunityModerator::get_person_moderated_communities(context.pool(), new_mod_id).await?;
|
||||
if !moderated_communities.contains(&community.id) {
|
||||
let form = CommunityModeratorForm {
|
||||
community_id: community.id,
|
||||
person_id: new_mod.id,
|
||||
};
|
||||
CommunityModerator::join(context.pool(), &form).await?;
|
||||
|
||||
// write mod log
|
||||
let actor = self
|
||||
.actor
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: actor.id,
|
||||
other_person_id: new_mod.id,
|
||||
community_id: community.id,
|
||||
removed: Some(false),
|
||||
};
|
||||
ModAddCommunity::create(context.pool(), &form).await?;
|
||||
}
|
||||
// TODO: send websocket notification about added mod
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for AddModToCommunity {
|
||||
type Response = AddModToCommunityResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
_response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community: ApubCommunity = Community::read(context.pool(), request.community_id)
|
||||
.await?
|
||||
.into();
|
||||
let updated_mod: ApubPerson = Person::read(context.pool(), request.person_id)
|
||||
.await?
|
||||
.into();
|
||||
if request.added {
|
||||
AddMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
RemoveMod::send(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
256
crates/apub/src/activities/community/collection_add.rs
Normal file
256
crates/apub/src/activities/community/collection_add.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::send_activity_in_community,
|
||||
generate_activity_id,
|
||||
verify_is_public,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{
|
||||
activities::{
|
||||
community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
|
||||
create_or_update::page::CreateOrUpdatePage,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
InCommunity,
|
||||
},
|
||||
ActorType,
|
||||
SendActivity,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
core::object_id::ObjectId,
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use activitystreams_kinds::{activity::AddType, public};
|
||||
use lemmy_api_common::{
|
||||
community::{AddModToCommunity, AddModToCommunityResponse},
|
||||
context::LemmyContext,
|
||||
post::{FeaturePost, PostResponse},
|
||||
utils::{generate_featured_url, generate_moderators_url, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::community::CollectionType,
|
||||
source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
person::Person,
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
impl CollectionAdd {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send_add_mod(
|
||||
community: &ApubCommunity,
|
||||
added_mod: &ApubPerson,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
AddType::Add,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let add = CollectionAdd {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: added_mod.actor_id(),
|
||||
target: generate_moderators_url(&community.actor_id)?.into(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: AddType::Add,
|
||||
id: id.clone(),
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::CollectionAdd(add);
|
||||
let inboxes = vec![added_mod.shared_inbox_or_inbox()];
|
||||
send_activity_in_community(activity, actor, community, inboxes, true, context).await
|
||||
}
|
||||
|
||||
pub async fn send_add_featured_post(
|
||||
community: &ApubCommunity,
|
||||
featured_post: &ApubPost,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
AddType::Add,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let add = CollectionAdd {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: featured_post.ap_id.clone().into(),
|
||||
target: generate_featured_url(&community.actor_id)?.into(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: AddType::Add,
|
||||
id: id.clone(),
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
let activity = AnnouncableActivities::CollectionAdd(add);
|
||||
send_activity_in_community(activity, actor, community, vec![], true, context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CollectionAdd {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
&self.object,
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let (community, collection_type) =
|
||||
Community::get_by_collection_url(context.pool(), &self.target.into()).await?;
|
||||
match collection_type {
|
||||
CollectionType::Moderators => {
|
||||
let new_mod = ObjectId::<ApubPerson>::new(self.object)
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
|
||||
// If we had to refetch the community while parsing the activity, then the new mod has already
|
||||
// been added. Skip it here as it would result in a duplicate key error.
|
||||
let new_mod_id = new_mod.id;
|
||||
let moderated_communities =
|
||||
CommunityModerator::get_person_moderated_communities(context.pool(), new_mod_id).await?;
|
||||
if !moderated_communities.contains(&community.id) {
|
||||
let form = CommunityModeratorForm {
|
||||
community_id: community.id,
|
||||
person_id: new_mod.id,
|
||||
};
|
||||
CommunityModerator::join(context.pool(), &form).await?;
|
||||
|
||||
// write mod log
|
||||
let actor = self
|
||||
.actor
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: actor.id,
|
||||
other_person_id: new_mod.id,
|
||||
community_id: community.id,
|
||||
removed: Some(false),
|
||||
};
|
||||
ModAddCommunity::create(context.pool(), &form).await?;
|
||||
}
|
||||
// TODO: send websocket notification about added mod
|
||||
}
|
||||
CollectionType::Featured => {
|
||||
let post = ObjectId::<ApubPost>::new(self.object)
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = PostUpdateForm::builder()
|
||||
.featured_community(Some(true))
|
||||
.build();
|
||||
Post::update(context.pool(), post.id, &form).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for AddModToCommunity {
|
||||
type Response = AddModToCommunityResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
_response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community: ApubCommunity = Community::read(context.pool(), request.community_id)
|
||||
.await?
|
||||
.into();
|
||||
let updated_mod: ApubPerson = Person::read(context.pool(), request.person_id)
|
||||
.await?
|
||||
.into();
|
||||
if request.added {
|
||||
CollectionAdd::send_add_mod(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
CollectionRemove::send_remove_mod(
|
||||
&community,
|
||||
&updated_mod,
|
||||
&local_user_view.person.into(),
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for FeaturePost {
|
||||
type Response = PostResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
// Deprecated, for backwards compatibility with 0.17
|
||||
CreateOrUpdatePage::send(
|
||||
&response.post_view.post,
|
||||
local_user_view.person.id,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
let community = Community::read(context.pool(), response.post_view.community.id)
|
||||
.await?
|
||||
.into();
|
||||
let post = response.post_view.post.clone().into();
|
||||
let person = local_user_view.person.into();
|
||||
if request.featured {
|
||||
CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
|
||||
} else {
|
||||
CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
|
||||
}
|
||||
}
|
||||
}
|
171
crates/apub/src/activities/community/collection_remove.rs
Normal file
171
crates/apub/src/activities/community/collection_remove.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::send_activity_in_community,
|
||||
generate_activity_id,
|
||||
verify_is_public,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{activities::community::collection_remove::CollectionRemove, InCommunity},
|
||||
ActorType,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
core::object_id::ObjectId,
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use activitystreams_kinds::{activity::RemoveType, public};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
utils::{generate_featured_url, generate_moderators_url},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::community::CollectionType,
|
||||
source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
impl CollectionRemove {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send_remove_mod(
|
||||
community: &ApubCommunity,
|
||||
removed_mod: &ApubPerson,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
RemoveType::Remove,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let remove = CollectionRemove {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: ObjectId::new(removed_mod.actor_id()),
|
||||
target: generate_moderators_url(&community.actor_id)?.into(),
|
||||
id: id.clone(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: RemoveType::Remove,
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::CollectionRemove(remove);
|
||||
let inboxes = vec![removed_mod.shared_inbox_or_inbox()];
|
||||
send_activity_in_community(activity, actor, community, inboxes, true, context).await
|
||||
}
|
||||
|
||||
pub async fn send_remove_featured_post(
|
||||
community: &ApubCommunity,
|
||||
featured_post: &ApubPost,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
RemoveType::Remove,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let remove = CollectionRemove {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: featured_post.ap_id.clone().into(),
|
||||
target: generate_featured_url(&community.actor_id)?.into(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: RemoveType::Remove,
|
||||
id: id.clone(),
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
let activity = AnnouncableActivities::CollectionRemove(remove);
|
||||
send_activity_in_community(activity, actor, community, vec![], true, context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CollectionRemove {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
self.object.inner(),
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let (community, collection_type) =
|
||||
Community::get_by_collection_url(context.pool(), &self.target.into()).await?;
|
||||
match collection_type {
|
||||
CollectionType::Moderators => {
|
||||
let remove_mod = self
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
|
||||
let form = CommunityModeratorForm {
|
||||
community_id: community.id,
|
||||
person_id: remove_mod.id,
|
||||
};
|
||||
CommunityModerator::leave(context.pool(), &form).await?;
|
||||
|
||||
// write mod log
|
||||
let actor = self
|
||||
.actor
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: actor.id,
|
||||
other_person_id: remove_mod.id,
|
||||
community_id: community.id,
|
||||
removed: Some(true),
|
||||
};
|
||||
ModAddCommunity::create(context.pool(), &form).await?;
|
||||
|
||||
// TODO: send websocket notification about removed mod
|
||||
}
|
||||
CollectionType::Featured => {
|
||||
let post = ObjectId::<ApubPost>::new(self.object)
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = PostUpdateForm::builder()
|
||||
.featured_community(Some(false))
|
||||
.build();
|
||||
Post::update(context.pool(), post.id, &form).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
200
crates/apub/src/activities/community/lock_page.rs
Normal file
200
crates/apub/src/activities/community/lock_page.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
check_community_deleted_or_removed,
|
||||
community::send_activity_in_community,
|
||||
generate_activity_id,
|
||||
verify_is_public,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
protocol::{
|
||||
activities::{
|
||||
community::lock_page::{LockPage, LockType, UndoLockPage},
|
||||
create_or_update::page::CreateOrUpdatePage,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
InCommunity,
|
||||
},
|
||||
SendActivity,
|
||||
};
|
||||
use activitypub_federation::{core::object_id::ObjectId, data::Data, traits::ActivityHandler};
|
||||
use activitystreams_kinds::{activity::UndoType, public};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
post::{LockPost, PostResponse},
|
||||
utils::get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::Community,
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for LockPage {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<Self::DataType>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), Self::Error> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
check_community_deleted_or_removed(&community)?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
self.object.inner(),
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<Self::DataType>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), Self::Error> {
|
||||
let form = PostUpdateForm::builder().locked(Some(true)).build();
|
||||
let post = self
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
Post::update(context.pool(), post.id, &form).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UndoLockPage {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<Self::DataType>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), Self::Error> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
check_community_deleted_or_removed(&community)?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
self.object.object.inner(),
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<Self::DataType>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), Self::Error> {
|
||||
let form = PostUpdateForm::builder().locked(Some(false)).build();
|
||||
let post = self
|
||||
.object
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
Post::update(context.pool(), post.id, &form).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for LockPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
// For backwards compat with 0.17
|
||||
CreateOrUpdatePage::send(
|
||||
&response.post_view.post,
|
||||
local_user_view.person.id,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
let id = generate_activity_id(
|
||||
LockType::Lock,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let community_id: Url = response.post_view.community.actor_id.clone().into();
|
||||
let actor = ObjectId::new(local_user_view.person.actor_id.clone());
|
||||
let lock = LockPage {
|
||||
actor,
|
||||
to: vec![public()],
|
||||
object: ObjectId::new(response.post_view.post.ap_id.clone()),
|
||||
cc: vec![community_id.clone()],
|
||||
kind: LockType::Lock,
|
||||
id,
|
||||
audience: Some(ObjectId::new(community_id)),
|
||||
};
|
||||
let activity = if request.locked {
|
||||
AnnouncableActivities::LockPost(lock)
|
||||
} else {
|
||||
let id = generate_activity_id(
|
||||
UndoType::Undo,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let undo = UndoLockPage {
|
||||
actor: lock.actor.clone(),
|
||||
to: vec![public()],
|
||||
cc: lock.cc.clone(),
|
||||
kind: UndoType::Undo,
|
||||
id,
|
||||
audience: lock.audience.clone(),
|
||||
object: lock,
|
||||
};
|
||||
AnnouncableActivities::UndoLockPost(undo)
|
||||
};
|
||||
let community = Community::read(context.pool(), response.post_view.community.id).await?;
|
||||
send_activity_in_community(
|
||||
activity,
|
||||
&local_user_view.person.into(),
|
||||
&community.into(),
|
||||
vec![],
|
||||
true,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
use crate::{
|
||||
activities::send_lemmy_activity,
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::community::announce::AnnounceActivity,
|
||||
};
|
||||
use activitypub_federation::{core::object_id::ObjectId, traits::Actor};
|
||||
use activitypub_federation::traits::Actor;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::person::PersonFollower;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
pub mod add_mod;
|
||||
pub mod announce;
|
||||
pub mod remove_mod;
|
||||
pub mod collection_add;
|
||||
pub mod collection_remove;
|
||||
pub mod lock_page;
|
||||
pub mod report;
|
||||
pub mod update;
|
||||
|
||||
|
@ -62,15 +62,3 @@ pub(crate) async fn send_activity_in_community(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn get_community_from_moderators_url(
|
||||
moderators: &Url,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let community_id = Url::parse(&moderators.to_string().replace("/moderators", ""))?;
|
||||
ObjectId::new(community_id)
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::send_activity_in_community,
|
||||
generate_activity_id,
|
||||
verify_add_remove_moderator_target,
|
||||
verify_is_public,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::community::remove_mod::RemoveMod, InCommunity},
|
||||
ActorType,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
core::object_id::ObjectId,
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use activitystreams_kinds::{activity::RemoveType, public};
|
||||
use lemmy_api_common::{context::LemmyContext, utils::generate_moderators_url};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{CommunityModerator, CommunityModeratorForm},
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
impl RemoveMod {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send(
|
||||
community: &ApubCommunity,
|
||||
removed_mod: &ApubPerson,
|
||||
actor: &ApubPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let id = generate_activity_id(
|
||||
RemoveType::Remove,
|
||||
&context.settings().get_protocol_and_hostname(),
|
||||
)?;
|
||||
let remove = RemoveMod {
|
||||
actor: ObjectId::new(actor.actor_id()),
|
||||
to: vec![public()],
|
||||
object: ObjectId::new(removed_mod.actor_id()),
|
||||
target: generate_moderators_url(&community.actor_id)?.into(),
|
||||
id: id.clone(),
|
||||
cc: vec![community.actor_id()],
|
||||
kind: RemoveType::Remove,
|
||||
audience: Some(ObjectId::new(community.actor_id())),
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::RemoveMod(remove);
|
||||
let inboxes = vec![removed_mod.shared_inbox_or_inbox()];
|
||||
send_activity_in_community(activity, actor, community, inboxes, true, context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for RemoveMod {
|
||||
type DataType = LemmyContext;
|
||||
type Error = LemmyError;
|
||||
|
||||
fn id(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn actor(&self) -> &Url {
|
||||
self.actor.inner()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context, request_counter).await?;
|
||||
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
|
||||
verify_mod_action(
|
||||
&self.actor,
|
||||
self.object.inner(),
|
||||
community.id,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
verify_add_remove_moderator_target(&self.target, &community)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(
|
||||
self,
|
||||
context: &Data<LemmyContext>,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = self.community(context, request_counter).await?;
|
||||
let remove_mod = self
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
|
||||
let form = CommunityModeratorForm {
|
||||
community_id: community.id,
|
||||
person_id: remove_mod.id,
|
||||
};
|
||||
CommunityModerator::leave(context.pool(), &form).await?;
|
||||
|
||||
// write mod log
|
||||
let actor = self
|
||||
.actor
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let form = ModAddCommunityForm {
|
||||
mod_person_id: actor.id,
|
||||
other_person_id: remove_mod.id,
|
||||
community_id: community.id,
|
||||
removed: Some(true),
|
||||
};
|
||||
ModAddCommunity::create(context.pool(), &form).await?;
|
||||
|
||||
// TODO: send websocket notification about removed mod
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -25,8 +25,7 @@ use activitypub_federation::{
|
|||
use activitystreams_kinds::public;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
post::{CreatePost, EditPost, FeaturePost, LockPost, PostResponse},
|
||||
utils::get_local_user_view_from_jwt,
|
||||
post::{CreatePost, EditPost, PostResponse},
|
||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
|
@ -79,48 +78,6 @@ impl SendActivity for EditPost {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for LockPost {
|
||||
type Response = PostResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
CreateOrUpdatePage::send(
|
||||
&response.post_view.post,
|
||||
local_user_view.person.id,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl SendActivity for FeaturePost {
|
||||
type Response = PostResponse;
|
||||
|
||||
async fn send_activity(
|
||||
request: &Self,
|
||||
response: &Self::Response,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
CreateOrUpdatePage::send(
|
||||
&response.post_view.post,
|
||||
local_user_view.person.id,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl CreateOrUpdatePage {
|
||||
pub(crate) async fn new(
|
||||
post: ApubPost,
|
||||
|
@ -145,7 +102,7 @@ impl CreateOrUpdatePage {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn send(
|
||||
pub(crate) async fn send(
|
||||
post: &Post,
|
||||
person_id: PersonId,
|
||||
kind: CreateOrUpdateType,
|
||||
|
|
|
@ -76,7 +76,7 @@ impl SendActivity for DeletePost {
|
|||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community = Community::read(context.pool(), response.post_view.community.id).await?;
|
||||
let deletable = DeletableObjects::Post(Box::new(response.post_view.post.clone().into()));
|
||||
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
|
||||
send_apub_delete_in_community(
|
||||
local_user_view.person,
|
||||
community,
|
||||
|
@ -101,7 +101,7 @@ impl SendActivity for RemovePost {
|
|||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community = Community::read(context.pool(), response.post_view.community.id).await?;
|
||||
let deletable = DeletableObjects::Post(Box::new(response.post_view.post.clone().into()));
|
||||
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
|
||||
send_apub_delete_in_community(
|
||||
local_user_view.person,
|
||||
community,
|
||||
|
@ -126,8 +126,7 @@ impl SendActivity for DeleteComment {
|
|||
let community_id = response.comment_view.community.id;
|
||||
let community = Community::read(context.pool(), community_id).await?;
|
||||
let person = Person::read(context.pool(), response.comment_view.creator.id).await?;
|
||||
let deletable =
|
||||
DeletableObjects::Comment(Box::new(response.comment_view.comment.clone().into()));
|
||||
let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into());
|
||||
send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
|
||||
.await
|
||||
}
|
||||
|
@ -146,7 +145,7 @@ impl SendActivity for RemoveComment {
|
|||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let comment = Comment::read(context.pool(), request.comment_id).await?;
|
||||
let community = Community::read(context.pool(), response.comment_view.community.id).await?;
|
||||
let deletable = DeletableObjects::Comment(Box::new(comment.into()));
|
||||
let deletable = DeletableObjects::Comment(comment.into());
|
||||
send_apub_delete_in_community(
|
||||
local_user_view.person,
|
||||
community,
|
||||
|
@ -192,7 +191,7 @@ impl SendActivity for DeleteCommunity {
|
|||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community = Community::read(context.pool(), request.community_id).await?;
|
||||
let deletable = DeletableObjects::Community(Box::new(community.clone().into()));
|
||||
let deletable = DeletableObjects::Community(community.clone().into());
|
||||
send_apub_delete_in_community(
|
||||
local_user_view.person,
|
||||
community,
|
||||
|
@ -217,7 +216,7 @@ impl SendActivity for RemoveCommunity {
|
|||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||
let community = Community::read(context.pool(), request.community_id).await?;
|
||||
let deletable = DeletableObjects::Community(Box::new(community.clone().into()));
|
||||
let deletable = DeletableObjects::Community(community.clone().into());
|
||||
send_apub_delete_in_community(
|
||||
local_user_view.person,
|
||||
community,
|
||||
|
@ -271,7 +270,7 @@ async fn send_apub_delete_private_message(
|
|||
let recipient_id = pm.recipient_id;
|
||||
let recipient: ApubPerson = Person::read(context.pool(), recipient_id).await?.into();
|
||||
|
||||
let deletable = DeletableObjects::PrivateMessage(Box::new(pm.into()));
|
||||
let deletable = DeletableObjects::PrivateMessage(pm.into());
|
||||
let inbox = vec![recipient.shared_inbox_or_inbox()];
|
||||
if deleted {
|
||||
let delete = Delete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
|
||||
|
@ -284,10 +283,10 @@ async fn send_apub_delete_private_message(
|
|||
}
|
||||
|
||||
pub enum DeletableObjects {
|
||||
Community(Box<ApubCommunity>),
|
||||
Comment(Box<ApubComment>),
|
||||
Post(Box<ApubPost>),
|
||||
PrivateMessage(Box<ApubPrivateMessage>),
|
||||
Community(ApubCommunity),
|
||||
Comment(ApubComment),
|
||||
Post(ApubPost),
|
||||
PrivateMessage(ApubPrivateMessage),
|
||||
}
|
||||
|
||||
impl DeletableObjects {
|
||||
|
@ -297,16 +296,16 @@ impl DeletableObjects {
|
|||
context: &LemmyContext,
|
||||
) -> Result<DeletableObjects, LemmyError> {
|
||||
if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? {
|
||||
return Ok(DeletableObjects::Community(Box::new(c)));
|
||||
return Ok(DeletableObjects::Community(c));
|
||||
}
|
||||
if let Some(p) = ApubPost::read_from_apub_id(ap_id.clone(), context).await? {
|
||||
return Ok(DeletableObjects::Post(Box::new(p)));
|
||||
return Ok(DeletableObjects::Post(p));
|
||||
}
|
||||
if let Some(c) = ApubComment::read_from_apub_id(ap_id.clone(), context).await? {
|
||||
return Ok(DeletableObjects::Comment(Box::new(c)));
|
||||
return Ok(DeletableObjects::Comment(c));
|
||||
}
|
||||
if let Some(p) = ApubPrivateMessage::read_from_apub_id(ap_id.clone(), context).await? {
|
||||
return Ok(DeletableObjects::PrivateMessage(Box::new(p)));
|
||||
return Ok(DeletableObjects::PrivateMessage(p));
|
||||
}
|
||||
Err(diesel::NotFound.into())
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use activitypub_federation::{
|
|||
};
|
||||
use activitystreams_kinds::public;
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::{context::LemmyContext, utils::generate_moderators_url};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::CommunityId,
|
||||
source::{community::Community, local_site::LocalSite},
|
||||
|
@ -111,18 +111,6 @@ pub(crate) async fn verify_mod_action(
|
|||
Err(LemmyError::from_message("Not a mod"))
|
||||
}
|
||||
|
||||
/// For Add/Remove community moderator activities, check that the target field actually contains
|
||||
/// /c/community/moderators. Any different values are unsupported.
|
||||
fn verify_add_remove_moderator_target(
|
||||
target: &Url,
|
||||
community: &ApubCommunity,
|
||||
) -> Result<(), LemmyError> {
|
||||
if target != &generate_moderators_url(&community.actor_id)?.into() {
|
||||
return Err(LemmyError::from_message("Unkown target url"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
|
||||
if ![to, cc].iter().any(|set| set.contains(&public())) {
|
||||
return Err(LemmyError::from_message("Object is not public"));
|
||||
|
@ -130,11 +118,15 @@ pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn verify_community_matches(
|
||||
a: &ApubCommunity,
|
||||
b: CommunityId,
|
||||
) -> Result<(), LemmyError> {
|
||||
if a.id != b {
|
||||
pub(crate) fn verify_community_matches<T>(
|
||||
a: &ObjectId<ApubCommunity>,
|
||||
b: T,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
T: Into<ObjectId<ApubCommunity>>,
|
||||
{
|
||||
let b: ObjectId<ApubCommunity> = b.into();
|
||||
if a != &b {
|
||||
return Err(LemmyError::from_message("Invalid community"));
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -4,9 +4,10 @@ use crate::{
|
|||
activities::{
|
||||
block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
community::{
|
||||
add_mod::AddMod,
|
||||
announce::{AnnounceActivity, RawAnnouncableActivities},
|
||||
remove_mod::RemoveMod,
|
||||
collection_add::CollectionAdd,
|
||||
collection_remove::CollectionRemove,
|
||||
lock_page::{LockPage, UndoLockPage},
|
||||
report::Report,
|
||||
update::UpdateCommunity,
|
||||
},
|
||||
|
@ -85,8 +86,10 @@ pub enum AnnouncableActivities {
|
|||
UpdateCommunity(UpdateCommunity),
|
||||
BlockUser(BlockUser),
|
||||
UndoBlockUser(UndoBlockUser),
|
||||
AddMod(AddMod),
|
||||
RemoveMod(RemoveMod),
|
||||
CollectionAdd(CollectionAdd),
|
||||
CollectionRemove(CollectionRemove),
|
||||
LockPost(LockPage),
|
||||
UndoLockPost(UndoLockPage),
|
||||
// For compatibility with Pleroma/Mastodon (send only)
|
||||
Page(Page),
|
||||
}
|
||||
|
@ -120,8 +123,10 @@ impl InCommunity for AnnouncableActivities {
|
|||
UpdateCommunity(a) => a.community(context, request_counter).await,
|
||||
BlockUser(a) => a.community(context, request_counter).await,
|
||||
UndoBlockUser(a) => a.community(context, request_counter).await,
|
||||
AddMod(a) => a.community(context, request_counter).await,
|
||||
RemoveMod(a) => a.community(context, request_counter).await,
|
||||
CollectionAdd(a) => a.community(context, request_counter).await,
|
||||
CollectionRemove(a) => a.community(context, request_counter).await,
|
||||
LockPost(a) => a.community(context, request_counter).await,
|
||||
UndoLockPost(a) => a.community(context, request_counter).await,
|
||||
Page(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
103
crates/apub/src/collections/community_featured.rs
Normal file
103
crates/apub/src/collections/community_featured.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
use crate::{
|
||||
collections::CommunityContext,
|
||||
objects::post::ApubPost,
|
||||
protocol::collections::group_featured::GroupFeatured,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
data::Data,
|
||||
traits::{ActivityHandler, ApubObject},
|
||||
utils::verify_domains_match,
|
||||
};
|
||||
use activitystreams_kinds::collection::OrderedCollectionType;
|
||||
use futures::future::{join_all, try_join_all};
|
||||
use lemmy_api_common::utils::generate_featured_url;
|
||||
use lemmy_db_schema::{source::post::Post, utils::FETCH_LIMIT_MAX};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ApubCommunityFeatured(Vec<ApubPost>);
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObject for ApubCommunityFeatured {
|
||||
type DataType = CommunityContext;
|
||||
type ApubType = GroupFeatured;
|
||||
type DbType = ();
|
||||
type Error = LemmyError;
|
||||
|
||||
async fn read_from_apub_id(
|
||||
_object_id: Url,
|
||||
data: &Self::DataType,
|
||||
) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Only read from database if its a local community, otherwise fetch over http
|
||||
if data.0.local {
|
||||
let community_id = data.0.id;
|
||||
let post_list: Vec<ApubPost> = Post::list_featured_for_community(data.1.pool(), community_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(Some(ApubCommunityFeatured(post_list)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, Self::Error> {
|
||||
let ordered_items = try_join_all(self.0.into_iter().map(|p| p.into_apub(&data.1))).await?;
|
||||
Ok(GroupFeatured {
|
||||
r#type: OrderedCollectionType::OrderedCollection,
|
||||
id: generate_featured_url(&data.0.actor_id)?.into(),
|
||||
total_items: ordered_items.len() as i32,
|
||||
ordered_items,
|
||||
})
|
||||
}
|
||||
|
||||
async fn verify(
|
||||
apub: &Self::ApubType,
|
||||
expected_domain: &Url,
|
||||
_data: &Self::DataType,
|
||||
_request_counter: &mut i32,
|
||||
) -> Result<(), Self::Error> {
|
||||
verify_domains_match(expected_domain, &apub.id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn from_apub(
|
||||
apub: Self::ApubType,
|
||||
data: &Self::DataType,
|
||||
_request_counter: &mut i32,
|
||||
) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut posts = apub.ordered_items;
|
||||
if posts.len() as i64 > FETCH_LIMIT_MAX {
|
||||
posts = posts[0..(FETCH_LIMIT_MAX as usize)].to_vec();
|
||||
}
|
||||
|
||||
// We intentionally ignore errors here. This is because the outbox might contain posts from old
|
||||
// Lemmy versions, or from other software which we cant parse. In that case, we simply skip the
|
||||
// item and only parse the ones that work.
|
||||
let data = Data::new(data.1.clone());
|
||||
// process items in parallel, to avoid long delay from fetch_site_metadata() and other processing
|
||||
join_all(posts.into_iter().map(|post| {
|
||||
async {
|
||||
// use separate request counter for each item, otherwise there will be problems with
|
||||
// parallel processing
|
||||
let request_counter = &mut 0;
|
||||
let verify = post.verify(&data, request_counter).await;
|
||||
if verify.is_ok() {
|
||||
post.receive(&data, request_counter).await.ok();
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await;
|
||||
|
||||
// This return value is unused, so just set an empty vec
|
||||
Ok(ApubCommunityFeatured(Vec::new()))
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ use lemmy_api_common::utils::generate_outbox_url;
|
|||
use lemmy_db_schema::{
|
||||
source::{person::Person, post::Post},
|
||||
traits::Crud,
|
||||
utils::FETCH_LIMIT_MAX,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
@ -35,6 +36,7 @@ impl ApubObject for ApubCommunityOutbox {
|
|||
type DataType = CommunityContext;
|
||||
type ApubType = GroupOutbox;
|
||||
type Error = LemmyError;
|
||||
type DbType = ();
|
||||
|
||||
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
||||
None
|
||||
|
@ -59,11 +61,6 @@ impl ApubObject for ApubCommunityOutbox {
|
|||
}
|
||||
}
|
||||
|
||||
async fn delete(self, _data: &Self::DataType) -> Result<(), LemmyError> {
|
||||
// do nothing (it gets deleted automatically with the community)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
|
||||
let mut ordered_items = vec![];
|
||||
|
@ -103,8 +100,8 @@ impl ApubObject for ApubCommunityOutbox {
|
|||
_request_counter: &mut i32,
|
||||
) -> Result<Self, LemmyError> {
|
||||
let mut outbox_activities = apub.ordered_items;
|
||||
if outbox_activities.len() > 20 {
|
||||
outbox_activities = outbox_activities[0..20].to_vec();
|
||||
if outbox_activities.len() as i64 > FETCH_LIMIT_MAX {
|
||||
outbox_activities = outbox_activities[0..(FETCH_LIMIT_MAX as usize)].to_vec();
|
||||
}
|
||||
|
||||
// We intentionally ignore errors here. This is because the outbox might contain posts from old
|
||||
|
@ -128,6 +125,4 @@ impl ApubObject for ApubCommunityOutbox {
|
|||
// This return value is unused, so just set an empty vec
|
||||
Ok(ApubCommunityOutbox(Vec::new()))
|
||||
}
|
||||
|
||||
type DbType = ();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::objects::community::ApubCommunity;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
|
||||
pub(crate) mod community_featured;
|
||||
pub(crate) mod community_moderators;
|
||||
pub(crate) mod community_outbox;
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
use crate::fetcher::post_or_comment::PostOrComment;
|
||||
use lemmy_db_queries::source::{
|
||||
comment::Comment_,
|
||||
community::Community_,
|
||||
person::Person_,
|
||||
post::Post_,
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::Comment,
|
||||
community::Community,
|
||||
person::Person,
|
||||
post::Post,
|
||||
site::Site,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_api_common::LemmyContext;
|
||||
|
||||
// TODO: merge this trait with ApubObject (means that db_schema needs to depend on apub_lib)
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait DeletableApubObject {
|
||||
// TODO: pass in tombstone with summary field, to decide between remove/delete
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for Community {
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let id = self.id;
|
||||
Community::update_deleted(context.pool(), id, true)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for Person {
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let id = self.id;
|
||||
Person::delete_account(context.pool(), id).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for Post {
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let id = self.id;
|
||||
Post::update_deleted(context.pool(), id, true)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for Comment {
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let id = self.id;
|
||||
Comment::update_deleted(context.pool(), id, true)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for PostOrComment {
|
||||
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
match self {
|
||||
PostOrComment::Comment(c) => {
|
||||
Comment::update_deleted(context.pool(), c.id, true)
|
||||
.await?;
|
||||
}
|
||||
PostOrComment::Post(p) => {
|
||||
Post::update_deleted(context.pool(), p.id, true)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl DeletableApubObject for Site {
|
||||
async fn delete(self, _context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
// not implemented, ignore
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -18,8 +18,8 @@ use url::Url;
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PostOrComment {
|
||||
Post(Box<ApubPost>),
|
||||
Comment(Box<ApubComment>),
|
||||
Post(ApubPost),
|
||||
Comment(ApubComment),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -40,7 +40,6 @@ impl ApubObject for PostOrComment {
|
|||
None
|
||||
}
|
||||
|
||||
// TODO: this can probably be implemented using a single sql query
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn read_from_apub_id(
|
||||
object_id: Url,
|
||||
|
@ -48,10 +47,10 @@ impl ApubObject for PostOrComment {
|
|||
) -> Result<Option<Self>, LemmyError> {
|
||||
let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?;
|
||||
Ok(match post {
|
||||
Some(o) => Some(PostOrComment::Post(Box::new(o))),
|
||||
Some(o) => Some(PostOrComment::Post(o)),
|
||||
None => ApubComment::read_from_apub_id(object_id, data)
|
||||
.await?
|
||||
.map(|c| PostOrComment::Comment(Box::new(c))),
|
||||
.map(PostOrComment::Comment),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -87,12 +86,12 @@ impl ApubObject for PostOrComment {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<Self, LemmyError> {
|
||||
Ok(match apub {
|
||||
PageOrNote::Page(p) => PostOrComment::Post(Box::new(
|
||||
ApubPost::from_apub(*p, context, request_counter).await?,
|
||||
)),
|
||||
PageOrNote::Note(n) => PostOrComment::Comment(Box::new(
|
||||
ApubComment::from_apub(n, context, request_counter).await?,
|
||||
)),
|
||||
PageOrNote::Page(p) => {
|
||||
PostOrComment::Post(ApubPost::from_apub(*p, context, request_counter).await?)
|
||||
}
|
||||
PageOrNote::Note(n) => {
|
||||
PostOrComment::Comment(ApubComment::from_apub(n, context, request_counter).await?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
activity_lists::GroupInboxActivities,
|
||||
collections::{
|
||||
community_featured::ApubCommunityFeatured,
|
||||
community_moderators::ApubCommunityModerators,
|
||||
community_outbox::ApubCommunityOutbox,
|
||||
CommunityContext,
|
||||
|
@ -16,7 +17,10 @@ use activitypub_federation::{
|
|||
traits::ApubObject,
|
||||
};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
utils::{generate_featured_url, generate_outbox_url},
|
||||
};
|
||||
use lemmy_db_schema::{source::community::Community, traits::ApubActor};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use serde::Deserialize;
|
||||
|
@ -106,3 +110,20 @@ pub(crate) async fn get_apub_community_moderators(
|
|||
&moderators.into_apub(&outbox_data).await?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns collection of featured (stickied) posts.
|
||||
pub(crate) async fn get_apub_community_featured(
|
||||
info: web::Path<CommunityQuery>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse, LemmyError> {
|
||||
let community = Community::read_from_name(context.pool(), &info.community_name, false).await?;
|
||||
if community.deleted || community.removed {
|
||||
return Err(LemmyError::from_message("deleted"));
|
||||
}
|
||||
let id = ObjectId::new(generate_featured_url(&community.actor_id)?);
|
||||
let data = CommunityContext(community.into(), context.get_ref().clone());
|
||||
let featured: ApubCommunityFeatured = id
|
||||
.dereference(&data, local_instance(&context).await, &mut 0)
|
||||
.await?;
|
||||
Ok(create_apub_response(&featured.into_apub(&data).await?))
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::http::{
|
|||
comment::get_apub_comment,
|
||||
community::{
|
||||
community_inbox,
|
||||
get_apub_community_featured,
|
||||
get_apub_community_followers,
|
||||
get_apub_community_http,
|
||||
get_apub_community_moderators,
|
||||
|
@ -37,6 +38,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||
"/c/{community_name}/outbox",
|
||||
web::get().to(get_apub_community_outbox),
|
||||
)
|
||||
.route(
|
||||
"/c/{community_name}/featured",
|
||||
web::get().to(get_apub_community_featured),
|
||||
)
|
||||
.route(
|
||||
"/c/{community_name}/moderators",
|
||||
web::get().to(get_apub_community_moderators),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
check_apub_id_valid_with_strictness,
|
||||
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
|
||||
collections::CommunityContext,
|
||||
fetch_local_site_data,
|
||||
local_instance,
|
||||
objects::instance::fetch_instance_actor_for_object,
|
||||
|
@ -20,7 +20,7 @@ use chrono::NaiveDateTime;
|
|||
use itertools::Itertools;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
utils::{generate_moderators_url, generate_outbox_url},
|
||||
utils::{generate_featured_url, generate_moderators_url, generate_outbox_url},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
|
@ -90,9 +90,6 @@ impl ApubObject for ApubCommunity {
|
|||
let community_id = self.id;
|
||||
let langs = CommunityLanguage::read(data.pool(), community_id).await?;
|
||||
let language = LanguageTag::new_multiple(langs, data.pool()).await?;
|
||||
let attributed_to = Some(ObjectId::<ApubCommunityModerators>::new(
|
||||
generate_moderators_url(&self.actor_id)?,
|
||||
));
|
||||
|
||||
let group = Group {
|
||||
kind: GroupType::Group,
|
||||
|
@ -104,7 +101,8 @@ impl ApubObject for ApubCommunity {
|
|||
icon: self.icon.clone().map(ImageObject::new),
|
||||
image: self.banner.clone().map(ImageObject::new),
|
||||
sensitive: Some(self.nsfw),
|
||||
moderators: attributed_to.clone(),
|
||||
moderators: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||
featured: Some(generate_featured_url(&self.actor_id)?.into()),
|
||||
inbox: self.inbox_url.clone().into(),
|
||||
outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?),
|
||||
followers: self.followers_url.clone().into(),
|
||||
|
@ -116,7 +114,7 @@ impl ApubObject for ApubCommunity {
|
|||
published: Some(convert_datetime(self.published)),
|
||||
updated: self.updated.map(convert_datetime),
|
||||
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
||||
attributed_to,
|
||||
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||
};
|
||||
Ok(group)
|
||||
}
|
||||
|
|
|
@ -49,18 +49,13 @@ impl InCommunity for BlockUser {
|
|||
.target
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let target_community = match target {
|
||||
let community = match target {
|
||||
SiteOrCommunity::Community(c) => c,
|
||||
SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()),
|
||||
};
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, target_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(target_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::block::block_user::BlockUser, InCommunity},
|
||||
};
|
||||
|
@ -35,15 +34,10 @@ impl InCommunity for UndoBlockUser {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let object_community = self.object.community(context, request_counter).await?;
|
||||
let community = self.object.community(context, request_counter).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
use crate::{
|
||||
activities::{community::get_community_from_moderators_url, verify_community_matches},
|
||||
local_instance,
|
||||
activities::verify_community_matches,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::InCommunity,
|
||||
};
|
||||
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
||||
use activitystreams_kinds::activity::AddType;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddMod {
|
||||
pub struct CollectionAdd {
|
||||
pub(crate) actor: ObjectId<ApubPerson>,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) to: Vec<Url>,
|
||||
pub(crate) object: ObjectId<ApubPerson>,
|
||||
pub(crate) object: Url,
|
||||
pub(crate) target: Url,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) cc: Vec<Url>,
|
||||
|
@ -28,22 +28,17 @@ pub struct AddMod {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl InCommunity for AddMod {
|
||||
impl InCommunity for CollectionAdd {
|
||||
async fn community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
_request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let mod_community =
|
||||
get_community_from_moderators_url(&self.target, context, request_counter).await?;
|
||||
let (community, _) =
|
||||
Community::get_by_collection_url(context.pool(), &self.clone().target.into()).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, mod_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(mod_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
use crate::{
|
||||
activities::{community::get_community_from_moderators_url, verify_community_matches},
|
||||
local_instance,
|
||||
activities::verify_community_matches,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::InCommunity,
|
||||
};
|
||||
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
||||
use activitystreams_kinds::activity::RemoveType;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveMod {
|
||||
pub struct CollectionRemove {
|
||||
pub(crate) actor: ObjectId<ApubPerson>,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) to: Vec<Url>,
|
||||
|
@ -28,22 +28,17 @@ pub struct RemoveMod {
|
|||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl InCommunity for RemoveMod {
|
||||
impl InCommunity for CollectionRemove {
|
||||
async fn community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
_request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let mod_community =
|
||||
get_community_from_moderators_url(&self.target, context, request_counter).await?;
|
||||
let (community, _) =
|
||||
Community::get_by_collection_url(context.pool(), &self.clone().target.into()).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, mod_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(mod_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
83
crates/apub/src/protocol/activities/community/lock_page.rs
Normal file
83
crates/apub/src/protocol/activities/community/lock_page.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::InCommunity,
|
||||
};
|
||||
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
||||
use activitystreams_kinds::activity::UndoType;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{source::community::Community, traits::Crud};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::Display;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Display)]
|
||||
pub enum LockType {
|
||||
Lock,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LockPage {
|
||||
pub(crate) actor: ObjectId<ApubPerson>,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) to: Vec<Url>,
|
||||
pub(crate) object: ObjectId<ApubPost>,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) kind: LockType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UndoLockPage {
|
||||
pub(crate) actor: ObjectId<ApubPerson>,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) to: Vec<Url>,
|
||||
pub(crate) object: LockPage,
|
||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) kind: UndoType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) audience: Option<ObjectId<ApubCommunity>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl InCommunity for LockPage {
|
||||
async fn community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let post = self
|
||||
.object
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
let community = Community::read(context.pool(), post.community_id).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl InCommunity for UndoLockPage {
|
||||
async fn community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let community = self.object.community(context, request_counter).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
pub mod add_mod;
|
||||
pub mod announce;
|
||||
pub mod remove_mod;
|
||||
pub mod collection_add;
|
||||
pub mod collection_remove;
|
||||
pub mod lock_page;
|
||||
pub mod report;
|
||||
pub mod update;
|
||||
|
||||
|
@ -8,9 +9,10 @@ pub mod update;
|
|||
mod tests {
|
||||
use crate::protocol::{
|
||||
activities::community::{
|
||||
add_mod::AddMod,
|
||||
announce::AnnounceActivity,
|
||||
remove_mod::RemoveMod,
|
||||
collection_add::CollectionAdd,
|
||||
collection_remove::CollectionRemove,
|
||||
lock_page::{LockPage, UndoLockPage},
|
||||
report::Report,
|
||||
update::UpdateCommunity,
|
||||
},
|
||||
|
@ -24,8 +26,22 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
test_parse_lemmy_item::<AddMod>("assets/lemmy/activities/community/add_mod.json").unwrap();
|
||||
test_parse_lemmy_item::<RemoveMod>("assets/lemmy/activities/community/remove_mod.json")
|
||||
test_parse_lemmy_item::<CollectionAdd>("assets/lemmy/activities/community/add_mod.json")
|
||||
.unwrap();
|
||||
test_parse_lemmy_item::<CollectionRemove>("assets/lemmy/activities/community/remove_mod.json")
|
||||
.unwrap();
|
||||
|
||||
test_parse_lemmy_item::<CollectionAdd>(
|
||||
"assets/lemmy/activities/community/add_featured_post.json",
|
||||
)
|
||||
.unwrap();
|
||||
test_parse_lemmy_item::<CollectionRemove>(
|
||||
"assets/lemmy/activities/community/remove_featured_post.json",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_parse_lemmy_item::<LockPage>("assets/lemmy/activities/community/lock_page.json").unwrap();
|
||||
test_parse_lemmy_item::<UndoLockPage>("assets/lemmy/activities/community/undo_lock_page.json")
|
||||
.unwrap();
|
||||
|
||||
test_parse_lemmy_item::<UpdateCommunity>(
|
||||
|
|
|
@ -33,17 +33,12 @@ impl InCommunity for Report {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let to_community = self.to[0]
|
||||
let community = self.to[0]
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, to_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(to_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,17 +36,12 @@ impl InCommunity for UpdateCommunity {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let object_community: ApubCommunity = ObjectId::new(self.object.id.clone())
|
||||
let community: ApubCommunity = ObjectId::new(self.object.id.clone())
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
mentions::MentionOrValue,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::CreateOrUpdateType, objects::note::Note, InCommunity},
|
||||
|
@ -37,15 +36,10 @@ impl InCommunity for CreateOrUpdateNote {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let post = self.object.get_parents(context, request_counter).await?.0;
|
||||
let community = Community::read(context.pool(), post.community_id).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, post.community_id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
let community = Community::read(context.pool(), post.community_id).await?;
|
||||
Ok(community.into())
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::CreateOrUpdateType, objects::page::Page, InCommunity},
|
||||
};
|
||||
|
@ -32,15 +31,10 @@ impl InCommunity for CreateOrUpdatePage {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let object_community = self.object.community(context, request_counter).await?;
|
||||
let community = self.object.community(context, request_counter).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::{deletion::DeletableObjects, verify_community_matches},
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{objects::tombstone::Tombstone, IdOrNestedObject, InCommunity},
|
||||
};
|
||||
|
@ -44,7 +43,7 @@ impl InCommunity for Delete {
|
|||
async fn community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
_request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? {
|
||||
DeletableObjects::Community(c) => c.id,
|
||||
|
@ -57,15 +56,10 @@ impl InCommunity for Delete {
|
|||
return Err(anyhow!("Private message is not part of community").into())
|
||||
}
|
||||
};
|
||||
let community = Community::read(context.pool(), community_id).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, community_id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
let community = Community::read(context.pool(), community_id).await?;
|
||||
Ok(community.into())
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::deletion::delete::Delete, InCommunity},
|
||||
};
|
||||
|
@ -37,15 +36,10 @@ impl InCommunity for UndoDelete {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let object_community = self.object.community(context, request_counter).await?;
|
||||
let community = self.object.community(context, request_counter).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
activities::verify_community_matches,
|
||||
local_instance,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::voting::vote::Vote, InCommunity},
|
||||
};
|
||||
|
@ -29,16 +28,10 @@ impl InCommunity for UndoVote {
|
|||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let local_instance = local_instance(context).await;
|
||||
let object_community = self.object.community(context, request_counter).await?;
|
||||
let community = self.object.community(context, request_counter).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,20 +59,15 @@ impl InCommunity for Vote {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let local_instance = local_instance(context).await;
|
||||
let object_community = self
|
||||
let community = self
|
||||
.object
|
||||
.dereference(context, local_instance, request_counter)
|
||||
.await?
|
||||
.community(context, request_counter)
|
||||
.await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, object_community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(object_community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
|
13
crates/apub/src/protocol/collections/group_featured.rs
Normal file
13
crates/apub/src/protocol/collections/group_featured.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::protocol::objects::page::Page;
|
||||
use activitystreams_kinds::collection::OrderedCollectionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupFeatured {
|
||||
pub(crate) r#type: OrderedCollectionType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) total_items: i32,
|
||||
pub(crate) ordered_items: Vec<Page>,
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub(crate) mod empty_outbox;
|
||||
pub(crate) mod group_featured;
|
||||
pub(crate) mod group_followers;
|
||||
pub(crate) mod group_moderators;
|
||||
pub(crate) mod group_outbox;
|
||||
|
@ -8,11 +9,12 @@ mod tests {
|
|||
use crate::protocol::{
|
||||
collections::{
|
||||
empty_outbox::EmptyOutbox,
|
||||
group_featured::GroupFeatured,
|
||||
group_followers::GroupFollowers,
|
||||
group_moderators::GroupModerators,
|
||||
group_outbox::GroupOutbox,
|
||||
},
|
||||
tests::test_parse_lemmy_item,
|
||||
tests::{test_json, test_parse_lemmy_item},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -22,8 +24,15 @@ mod tests {
|
|||
let outbox =
|
||||
test_parse_lemmy_item::<GroupOutbox>("assets/lemmy/collections/group_outbox.json").unwrap();
|
||||
assert_eq!(outbox.ordered_items.len() as i32, outbox.total_items);
|
||||
test_parse_lemmy_item::<GroupFeatured>("assets/lemmy/collections/group_featured_posts.json")
|
||||
.unwrap();
|
||||
test_parse_lemmy_item::<GroupModerators>("assets/lemmy/collections/group_moderators.json")
|
||||
.unwrap();
|
||||
test_parse_lemmy_item::<EmptyOutbox>("assets/lemmy/collections/person_outbox.json").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mastodon_collections() {
|
||||
test_json::<GroupFeatured>("assets/mastodon/collections/featured.json").unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
check_apub_id_valid_with_strictness,
|
||||
collections::{
|
||||
community_featured::ApubCommunityFeatured,
|
||||
community_moderators::ApubCommunityModerators,
|
||||
community_outbox::ApubCommunityOutbox,
|
||||
},
|
||||
|
@ -65,6 +66,7 @@ pub struct Group {
|
|||
pub(crate) posting_restricted_to_mods: Option<bool>,
|
||||
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
|
||||
pub(crate) endpoints: Option<Endpoints>,
|
||||
pub(crate) featured: Option<ObjectId<ApubCommunityFeatured>>,
|
||||
#[serde(default)]
|
||||
pub(crate) language: Vec<LanguageTag>,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
|
@ -117,8 +119,10 @@ impl Group {
|
|||
followers_url: Some(self.followers.into()),
|
||||
inbox_url: Some(self.inbox.into()),
|
||||
shared_inbox_url: self.endpoints.map(|e| e.shared_inbox.into()),
|
||||
moderators_url: self.moderators.map(Into::into),
|
||||
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||
instance_id,
|
||||
featured_url: self.featured.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +150,9 @@ impl Group {
|
|||
followers_url: Some(self.followers.into()),
|
||||
inbox_url: Some(self.inbox.into()),
|
||||
shared_inbox_url: Some(self.endpoints.map(|e| e.shared_inbox.into())),
|
||||
moderators_url: self.moderators.map(Into::into),
|
||||
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||
featured_url: self.featured.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,15 +67,11 @@ impl Note {
|
|||
.await?,
|
||||
);
|
||||
match parent.deref() {
|
||||
PostOrComment::Post(p) => {
|
||||
let post = p.deref().clone();
|
||||
Ok((post, None))
|
||||
}
|
||||
PostOrComment::Post(p) => Ok((p.clone(), None)),
|
||||
PostOrComment::Comment(c) => {
|
||||
let post_id = c.post_id;
|
||||
let post = Post::read(context.pool(), post_id).await?;
|
||||
let comment = c.deref().clone();
|
||||
Ok((post.into(), Some(comment)))
|
||||
Ok((post.into(), Some(c.clone())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,15 +85,10 @@ impl InCommunity for Note {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let (post, _) = self.get_parents(context, request_counter).await?;
|
||||
let community_id = post.community_id;
|
||||
let community = Community::read(context.pool(), post.community_id).await?;
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, local_instance(context).await, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, community_id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(Community::read(context.pool(), community_id).await?.into())
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ pub struct Page {
|
|||
pub(crate) image: Option<ImageObject>,
|
||||
pub(crate) comments_enabled: Option<bool>,
|
||||
pub(crate) sensitive: Option<bool>,
|
||||
/// Deprecated, for compatibility with Lemmy 0.17
|
||||
pub(crate) stickied: Option<bool>,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||
|
@ -252,14 +253,9 @@ impl InCommunity for Page {
|
|||
}
|
||||
};
|
||||
if let Some(audience) = &self.audience {
|
||||
let audience = audience
|
||||
.dereference(context, instance, request_counter)
|
||||
.await?;
|
||||
verify_community_matches(&audience, community.id)?;
|
||||
Ok(audience)
|
||||
} else {
|
||||
Ok(community)
|
||||
verify_community_matches(audience, community.actor_id.clone())?;
|
||||
}
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -194,6 +194,38 @@ impl DeleteableOrRemoveable for Community {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum CollectionType {
|
||||
Moderators,
|
||||
Featured,
|
||||
}
|
||||
|
||||
impl Community {
|
||||
/// Get the community which has a given moderators or featured url, also return the collection type
|
||||
pub async fn get_by_collection_url(
|
||||
pool: &DbPool,
|
||||
url: &DbUrl,
|
||||
) -> Result<(Community, CollectionType), Error> {
|
||||
use crate::schema::community::dsl::{featured_url, moderators_url};
|
||||
use CollectionType::*;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let res = community
|
||||
.filter(moderators_url.eq(url))
|
||||
.first::<Self>(conn)
|
||||
.await;
|
||||
if let Ok(c) = res {
|
||||
return Ok((c, Moderators));
|
||||
}
|
||||
let res = community
|
||||
.filter(featured_url.eq(url))
|
||||
.first::<Self>(conn)
|
||||
.await;
|
||||
if let Ok(c) = res {
|
||||
return Ok((c, Featured));
|
||||
}
|
||||
Err(diesel::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommunityModerator {
|
||||
pub async fn delete_for_community(
|
||||
pool: &DbPool,
|
||||
|
@ -430,6 +462,8 @@ mod tests {
|
|||
followers_url: inserted_community.followers_url.clone(),
|
||||
inbox_url: inserted_community.inbox_url.clone(),
|
||||
shared_inbox_url: None,
|
||||
moderators_url: None,
|
||||
featured_url: None,
|
||||
hidden: false,
|
||||
posting_restricted_to_mods: false,
|
||||
instance_id: inserted_instance.id,
|
||||
|
|
|
@ -89,6 +89,22 @@ impl Post {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn list_featured_for_community(
|
||||
pool: &DbPool,
|
||||
the_community_id: CommunityId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
post
|
||||
.filter(community_id.eq(the_community_id))
|
||||
.filter(deleted.eq(false))
|
||||
.filter(removed.eq(false))
|
||||
.filter(featured_community.eq(true))
|
||||
.then_order_by(published.desc())
|
||||
.limit(FETCH_LIMIT_MAX)
|
||||
.load::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn permadelete_for_creator(
|
||||
pool: &DbPool,
|
||||
for_creator_id: PersonId,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#[cfg(feature = "full")]
|
||||
use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel_ltree::Ltree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -110,7 +112,7 @@ pub struct LocalSiteId(i32);
|
|||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
||||
#[cfg_attr(feature = "full", diesel(sql_type = diesel::sql_types::Text))]
|
||||
pub struct DbUrl(pub(crate) Url);
|
||||
pub struct DbUrl(pub(crate) Box<Url>);
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -128,13 +130,23 @@ impl Display for DbUrl {
|
|||
#[allow(clippy::from_over_into)]
|
||||
impl Into<DbUrl> for Url {
|
||||
fn into(self) -> DbUrl {
|
||||
DbUrl(self)
|
||||
DbUrl(Box::new(self))
|
||||
}
|
||||
}
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<Url> for DbUrl {
|
||||
fn into(self) -> Url {
|
||||
self.0
|
||||
*self.0
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "full")]
|
||||
impl<T> From<DbUrl> for ObjectId<T>
|
||||
where
|
||||
T: ApubObject + Send,
|
||||
for<'de2> <T as ApubObject>::ApubType: Deserialize<'de2>,
|
||||
{
|
||||
fn from(value: DbUrl) -> Self {
|
||||
ObjectId::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,8 @@ table! {
|
|||
followers_url -> Varchar,
|
||||
inbox_url -> Varchar,
|
||||
shared_inbox_url -> Nullable<Varchar>,
|
||||
moderators_url -> Nullable<Varchar>,
|
||||
featured_url -> Nullable<Varchar>,
|
||||
hidden -> Bool,
|
||||
posting_restricted_to_mods -> Bool,
|
||||
instance_id -> Int4,
|
||||
|
|
|
@ -27,6 +27,12 @@ pub struct Community {
|
|||
pub followers_url: DbUrl,
|
||||
pub inbox_url: DbUrl,
|
||||
pub shared_inbox_url: Option<DbUrl>,
|
||||
/// Url where moderators collection is served over Activitypub
|
||||
#[serde(skip)]
|
||||
pub moderators_url: Option<DbUrl>,
|
||||
/// Url where featured posts collection is served over Activitypub
|
||||
#[serde(skip)]
|
||||
pub featured_url: Option<DbUrl>,
|
||||
pub hidden: bool,
|
||||
pub posting_restricted_to_mods: bool,
|
||||
pub instance_id: InstanceId,
|
||||
|
@ -80,6 +86,8 @@ pub struct CommunityInsertForm {
|
|||
pub followers_url: Option<DbUrl>,
|
||||
pub inbox_url: Option<DbUrl>,
|
||||
pub shared_inbox_url: Option<DbUrl>,
|
||||
pub moderators_url: Option<DbUrl>,
|
||||
pub featured_url: Option<DbUrl>,
|
||||
pub hidden: Option<bool>,
|
||||
pub posting_restricted_to_mods: Option<bool>,
|
||||
#[builder(!default)]
|
||||
|
@ -108,6 +116,8 @@ pub struct CommunityUpdateForm {
|
|||
pub followers_url: Option<DbUrl>,
|
||||
pub inbox_url: Option<DbUrl>,
|
||||
pub shared_inbox_url: Option<Option<DbUrl>>,
|
||||
pub moderators_url: Option<DbUrl>,
|
||||
pub featured_url: Option<DbUrl>,
|
||||
pub hidden: Option<bool>,
|
||||
pub posting_restricted_to_mods: Option<bool>,
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ where
|
|||
{
|
||||
fn from_sql(value: diesel::backend::RawValue<'_, DB>) -> diesel::deserialize::Result<Self> {
|
||||
let str = String::from_sql(value)?;
|
||||
Ok(DbUrl(Url::parse(&str)?))
|
||||
Ok(DbUrl(Box::new(Url::parse(&str)?)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ where
|
|||
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||
{
|
||||
fn from(id: ObjectId<Kind>) -> Self {
|
||||
DbUrl(id.into())
|
||||
DbUrl(Box::new(id.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
depends_on:
|
||||
- postgres_alpha
|
||||
restart: always
|
||||
ports:
|
||||
- "8541:8541"
|
||||
postgres_alpha:
|
||||
|
@ -73,6 +74,7 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
depends_on:
|
||||
- postgres_beta
|
||||
restart: always
|
||||
ports:
|
||||
- "8551:8551"
|
||||
postgres_beta:
|
||||
|
@ -102,6 +104,7 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
depends_on:
|
||||
- postgres_gamma
|
||||
restart: always
|
||||
ports:
|
||||
- "8561:8561"
|
||||
postgres_gamma:
|
||||
|
@ -132,6 +135,7 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
depends_on:
|
||||
- postgres_delta
|
||||
restart: always
|
||||
ports:
|
||||
- "8571:8571"
|
||||
postgres_delta:
|
||||
|
@ -162,6 +166,7 @@ services:
|
|||
- RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
depends_on:
|
||||
- postgres_epsilon
|
||||
restart: always
|
||||
ports:
|
||||
- "8581:8581"
|
||||
postgres_epsilon:
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
alter table community drop column moderators_url;
|
||||
alter table community drop column featured_url;
|
|
@ -0,0 +1,2 @@
|
|||
alter table community add column moderators_url varchar(255) unique;
|
||||
alter table community add column featured_url varchar(255) unique;
|
Loading…
Reference in a new issue