mirror of https://github.com/LemmyNet/lemmy
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>verify-distinguish^2
parent
8409e50f8c
commit
62663a9f2e
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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,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(())
|
||||
}
|
||||
}
|
@ -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()))
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>,
|
||||
}
|
@ -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 New Issue