mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 12:21:18 +00:00
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())
|
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> {
|
pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
|
||||||
Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
|
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",
|
"followers": "https://enterprise.lemmy.ml/c/tenforward/followers",
|
||||||
"moderators": "https://enterprise.lemmy.ml/c/tenforward/moderators",
|
"moderators": "https://enterprise.lemmy.ml/c/tenforward/moderators",
|
||||||
"attributedTo": "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,
|
"postingRestrictedToMods": false,
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"sharedInbox": "https://enterprise.lemmy.ml/inbox"
|
"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::{
|
use crate::{
|
||||||
activities::send_lemmy_activity,
|
activities::send_lemmy_activity,
|
||||||
activity_lists::AnnouncableActivities,
|
activity_lists::AnnouncableActivities,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::activities::community::announce::AnnounceActivity,
|
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_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::source::person::PersonFollower;
|
use lemmy_db_schema::source::person::PersonFollower;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub mod add_mod;
|
|
||||||
pub mod announce;
|
pub mod announce;
|
||||||
pub mod remove_mod;
|
pub mod collection_add;
|
||||||
|
pub mod collection_remove;
|
||||||
|
pub mod lock_page;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
||||||
|
@ -62,15 +62,3 @@ pub(crate) async fn send_activity_in_community(
|
||||||
|
|
||||||
Ok(())
|
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 activitystreams_kinds::public;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{CreatePost, EditPost, FeaturePost, LockPost, PostResponse},
|
post::{CreatePost, EditPost, PostResponse},
|
||||||
utils::get_local_user_view_from_jwt,
|
|
||||||
websocket::{send::send_post_ws_message, UserOperationCrud},
|
websocket::{send::send_post_ws_message, UserOperationCrud},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
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 {
|
impl CreateOrUpdatePage {
|
||||||
pub(crate) async fn new(
|
pub(crate) async fn new(
|
||||||
post: ApubPost,
|
post: ApubPost,
|
||||||
|
@ -145,7 +102,7 @@ impl CreateOrUpdatePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn send(
|
pub(crate) async fn send(
|
||||||
post: &Post,
|
post: &Post,
|
||||||
person_id: PersonId,
|
person_id: PersonId,
|
||||||
kind: CreateOrUpdateType,
|
kind: CreateOrUpdateType,
|
||||||
|
|
|
@ -76,7 +76,7 @@ impl SendActivity for DeletePost {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
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 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(
|
send_apub_delete_in_community(
|
||||||
local_user_view.person,
|
local_user_view.person,
|
||||||
community,
|
community,
|
||||||
|
@ -101,7 +101,7 @@ impl SendActivity for RemovePost {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
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 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(
|
send_apub_delete_in_community(
|
||||||
local_user_view.person,
|
local_user_view.person,
|
||||||
community,
|
community,
|
||||||
|
@ -126,8 +126,7 @@ impl SendActivity for DeleteComment {
|
||||||
let community_id = response.comment_view.community.id;
|
let community_id = response.comment_view.community.id;
|
||||||
let community = Community::read(context.pool(), community_id).await?;
|
let community = Community::read(context.pool(), community_id).await?;
|
||||||
let person = Person::read(context.pool(), response.comment_view.creator.id).await?;
|
let person = Person::read(context.pool(), response.comment_view.creator.id).await?;
|
||||||
let deletable =
|
let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into());
|
||||||
DeletableObjects::Comment(Box::new(response.comment_view.comment.clone().into()));
|
|
||||||
send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
|
send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -146,7 +145,7 @@ impl SendActivity for RemoveComment {
|
||||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||||
let comment = Comment::read(context.pool(), request.comment_id).await?;
|
let comment = Comment::read(context.pool(), request.comment_id).await?;
|
||||||
let community = Community::read(context.pool(), response.comment_view.community.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(
|
send_apub_delete_in_community(
|
||||||
local_user_view.person,
|
local_user_view.person,
|
||||||
community,
|
community,
|
||||||
|
@ -192,7 +191,7 @@ impl SendActivity for DeleteCommunity {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||||
let community = Community::read(context.pool(), request.community_id).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(
|
send_apub_delete_in_community(
|
||||||
local_user_view.person,
|
local_user_view.person,
|
||||||
community,
|
community,
|
||||||
|
@ -217,7 +216,7 @@ impl SendActivity for RemoveCommunity {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&request.auth, context.pool(), context.secret()).await?;
|
||||||
let community = Community::read(context.pool(), request.community_id).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(
|
send_apub_delete_in_community(
|
||||||
local_user_view.person,
|
local_user_view.person,
|
||||||
community,
|
community,
|
||||||
|
@ -271,7 +270,7 @@ async fn send_apub_delete_private_message(
|
||||||
let recipient_id = pm.recipient_id;
|
let recipient_id = pm.recipient_id;
|
||||||
let recipient: ApubPerson = Person::read(context.pool(), recipient_id).await?.into();
|
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()];
|
let inbox = vec![recipient.shared_inbox_or_inbox()];
|
||||||
if deleted {
|
if deleted {
|
||||||
let delete = Delete::new(actor, deletable, recipient.actor_id(), None, None, context)?;
|
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 {
|
pub enum DeletableObjects {
|
||||||
Community(Box<ApubCommunity>),
|
Community(ApubCommunity),
|
||||||
Comment(Box<ApubComment>),
|
Comment(ApubComment),
|
||||||
Post(Box<ApubPost>),
|
Post(ApubPost),
|
||||||
PrivateMessage(Box<ApubPrivateMessage>),
|
PrivateMessage(ApubPrivateMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeletableObjects {
|
impl DeletableObjects {
|
||||||
|
@ -297,16 +296,16 @@ impl DeletableObjects {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<DeletableObjects, LemmyError> {
|
) -> Result<DeletableObjects, LemmyError> {
|
||||||
if let Some(c) = ApubCommunity::read_from_apub_id(ap_id.clone(), context).await? {
|
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? {
|
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? {
|
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? {
|
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())
|
Err(diesel::NotFound.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use activitypub_federation::{
|
||||||
};
|
};
|
||||||
use activitystreams_kinds::public;
|
use activitystreams_kinds::public;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_common::{context::LemmyContext, utils::generate_moderators_url};
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::CommunityId,
|
newtypes::CommunityId,
|
||||||
source::{community::Community, local_site::LocalSite},
|
source::{community::Community, local_site::LocalSite},
|
||||||
|
@ -111,18 +111,6 @@ pub(crate) async fn verify_mod_action(
|
||||||
Err(LemmyError::from_message("Not a mod"))
|
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> {
|
pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
|
||||||
if ![to, cc].iter().any(|set| set.contains(&public())) {
|
if ![to, cc].iter().any(|set| set.contains(&public())) {
|
||||||
return Err(LemmyError::from_message("Object is not 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_community_matches(
|
pub(crate) fn verify_community_matches<T>(
|
||||||
a: &ApubCommunity,
|
a: &ObjectId<ApubCommunity>,
|
||||||
b: CommunityId,
|
b: T,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError>
|
||||||
if a.id != b {
|
where
|
||||||
|
T: Into<ObjectId<ApubCommunity>>,
|
||||||
|
{
|
||||||
|
let b: ObjectId<ApubCommunity> = b.into();
|
||||||
|
if a != &b {
|
||||||
return Err(LemmyError::from_message("Invalid community"));
|
return Err(LemmyError::from_message("Invalid community"));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -4,9 +4,10 @@ use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||||
community::{
|
community::{
|
||||||
add_mod::AddMod,
|
|
||||||
announce::{AnnounceActivity, RawAnnouncableActivities},
|
announce::{AnnounceActivity, RawAnnouncableActivities},
|
||||||
remove_mod::RemoveMod,
|
collection_add::CollectionAdd,
|
||||||
|
collection_remove::CollectionRemove,
|
||||||
|
lock_page::{LockPage, UndoLockPage},
|
||||||
report::Report,
|
report::Report,
|
||||||
update::UpdateCommunity,
|
update::UpdateCommunity,
|
||||||
},
|
},
|
||||||
|
@ -85,8 +86,10 @@ pub enum AnnouncableActivities {
|
||||||
UpdateCommunity(UpdateCommunity),
|
UpdateCommunity(UpdateCommunity),
|
||||||
BlockUser(BlockUser),
|
BlockUser(BlockUser),
|
||||||
UndoBlockUser(UndoBlockUser),
|
UndoBlockUser(UndoBlockUser),
|
||||||
AddMod(AddMod),
|
CollectionAdd(CollectionAdd),
|
||||||
RemoveMod(RemoveMod),
|
CollectionRemove(CollectionRemove),
|
||||||
|
LockPost(LockPage),
|
||||||
|
UndoLockPost(UndoLockPage),
|
||||||
// For compatibility with Pleroma/Mastodon (send only)
|
// For compatibility with Pleroma/Mastodon (send only)
|
||||||
Page(Page),
|
Page(Page),
|
||||||
}
|
}
|
||||||
|
@ -120,8 +123,10 @@ impl InCommunity for AnnouncableActivities {
|
||||||
UpdateCommunity(a) => a.community(context, request_counter).await,
|
UpdateCommunity(a) => a.community(context, request_counter).await,
|
||||||
BlockUser(a) => a.community(context, request_counter).await,
|
BlockUser(a) => a.community(context, request_counter).await,
|
||||||
UndoBlockUser(a) => a.community(context, request_counter).await,
|
UndoBlockUser(a) => a.community(context, request_counter).await,
|
||||||
AddMod(a) => a.community(context, request_counter).await,
|
CollectionAdd(a) => a.community(context, request_counter).await,
|
||||||
RemoveMod(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!(),
|
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::{
|
use lemmy_db_schema::{
|
||||||
source::{person::Person, post::Post},
|
source::{person::Person, post::Post},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
utils::FETCH_LIMIT_MAX,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -35,6 +36,7 @@ impl ApubObject for ApubCommunityOutbox {
|
||||||
type DataType = CommunityContext;
|
type DataType = CommunityContext;
|
||||||
type ApubType = GroupOutbox;
|
type ApubType = GroupOutbox;
|
||||||
type Error = LemmyError;
|
type Error = LemmyError;
|
||||||
|
type DbType = ();
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
|
||||||
None
|
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)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
|
async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
|
||||||
let mut ordered_items = vec![];
|
let mut ordered_items = vec![];
|
||||||
|
@ -103,8 +100,8 @@ impl ApubObject for ApubCommunityOutbox {
|
||||||
_request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<Self, LemmyError> {
|
) -> Result<Self, LemmyError> {
|
||||||
let mut outbox_activities = apub.ordered_items;
|
let mut outbox_activities = apub.ordered_items;
|
||||||
if outbox_activities.len() > 20 {
|
if outbox_activities.len() as i64 > FETCH_LIMIT_MAX {
|
||||||
outbox_activities = outbox_activities[0..20].to_vec();
|
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
|
// 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
|
// This return value is unused, so just set an empty vec
|
||||||
Ok(ApubCommunityOutbox(Vec::new()))
|
Ok(ApubCommunityOutbox(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
type DbType = ();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::objects::community::ApubCommunity;
|
use crate::objects::community::ApubCommunity;
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
|
||||||
|
pub(crate) mod community_featured;
|
||||||
pub(crate) mod community_moderators;
|
pub(crate) mod community_moderators;
|
||||||
pub(crate) mod community_outbox;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PostOrComment {
|
pub enum PostOrComment {
|
||||||
Post(Box<ApubPost>),
|
Post(ApubPost),
|
||||||
Comment(Box<ApubComment>),
|
Comment(ApubComment),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -40,7 +40,6 @@ impl ApubObject for PostOrComment {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this can probably be implemented using a single sql query
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn read_from_apub_id(
|
async fn read_from_apub_id(
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
|
@ -48,10 +47,10 @@ impl ApubObject for PostOrComment {
|
||||||
) -> Result<Option<Self>, LemmyError> {
|
) -> Result<Option<Self>, LemmyError> {
|
||||||
let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?;
|
let post = ApubPost::read_from_apub_id(object_id.clone(), data).await?;
|
||||||
Ok(match post {
|
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)
|
None => ApubComment::read_from_apub_id(object_id, data)
|
||||||
.await?
|
.await?
|
||||||
.map(|c| PostOrComment::Comment(Box::new(c))),
|
.map(PostOrComment::Comment),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,12 +86,12 @@ impl ApubObject for PostOrComment {
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<Self, LemmyError> {
|
) -> Result<Self, LemmyError> {
|
||||||
Ok(match apub {
|
Ok(match apub {
|
||||||
PageOrNote::Page(p) => PostOrComment::Post(Box::new(
|
PageOrNote::Page(p) => {
|
||||||
ApubPost::from_apub(*p, context, request_counter).await?,
|
PostOrComment::Post(ApubPost::from_apub(*p, context, request_counter).await?)
|
||||||
)),
|
}
|
||||||
PageOrNote::Note(n) => PostOrComment::Comment(Box::new(
|
PageOrNote::Note(n) => {
|
||||||
ApubComment::from_apub(n, context, request_counter).await?,
|
PostOrComment::Comment(ApubComment::from_apub(n, context, request_counter).await?)
|
||||||
)),
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::GroupInboxActivities,
|
activity_lists::GroupInboxActivities,
|
||||||
collections::{
|
collections::{
|
||||||
|
community_featured::ApubCommunityFeatured,
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
CommunityContext,
|
CommunityContext,
|
||||||
|
@ -16,7 +17,10 @@ use activitypub_federation::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
};
|
};
|
||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
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_db_schema::{source::community::Community, traits::ApubActor};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -106,3 +110,20 @@ pub(crate) async fn get_apub_community_moderators(
|
||||||
&moderators.into_apub(&outbox_data).await?,
|
&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,
|
comment::get_apub_comment,
|
||||||
community::{
|
community::{
|
||||||
community_inbox,
|
community_inbox,
|
||||||
|
get_apub_community_featured,
|
||||||
get_apub_community_followers,
|
get_apub_community_followers,
|
||||||
get_apub_community_http,
|
get_apub_community_http,
|
||||||
get_apub_community_moderators,
|
get_apub_community_moderators,
|
||||||
|
@ -37,6 +38,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
"/c/{community_name}/outbox",
|
"/c/{community_name}/outbox",
|
||||||
web::get().to(get_apub_community_outbox),
|
web::get().to(get_apub_community_outbox),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/c/{community_name}/featured",
|
||||||
|
web::get().to(get_apub_community_featured),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/c/{community_name}/moderators",
|
"/c/{community_name}/moderators",
|
||||||
web::get().to(get_apub_community_moderators),
|
web::get().to(get_apub_community_moderators),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
|
collections::CommunityContext,
|
||||||
fetch_local_site_data,
|
fetch_local_site_data,
|
||||||
local_instance,
|
local_instance,
|
||||||
objects::instance::fetch_instance_actor_for_object,
|
objects::instance::fetch_instance_actor_for_object,
|
||||||
|
@ -20,7 +20,7 @@ use chrono::NaiveDateTime;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{generate_moderators_url, generate_outbox_url},
|
utils::{generate_featured_url, generate_moderators_url, generate_outbox_url},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -90,9 +90,6 @@ impl ApubObject for ApubCommunity {
|
||||||
let community_id = self.id;
|
let community_id = self.id;
|
||||||
let langs = CommunityLanguage::read(data.pool(), community_id).await?;
|
let langs = CommunityLanguage::read(data.pool(), community_id).await?;
|
||||||
let language = LanguageTag::new_multiple(langs, data.pool()).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 {
|
let group = Group {
|
||||||
kind: GroupType::Group,
|
kind: GroupType::Group,
|
||||||
|
@ -104,7 +101,8 @@ impl ApubObject for ApubCommunity {
|
||||||
icon: self.icon.clone().map(ImageObject::new),
|
icon: self.icon.clone().map(ImageObject::new),
|
||||||
image: self.banner.clone().map(ImageObject::new),
|
image: self.banner.clone().map(ImageObject::new),
|
||||||
sensitive: Some(self.nsfw),
|
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(),
|
inbox: self.inbox_url.clone().into(),
|
||||||
outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?),
|
outbox: ObjectId::new(generate_outbox_url(&self.actor_id)?),
|
||||||
followers: self.followers_url.clone().into(),
|
followers: self.followers_url.clone().into(),
|
||||||
|
@ -116,7 +114,7 @@ impl ApubObject for ApubCommunity {
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(convert_datetime(self.published)),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated.map(convert_datetime),
|
||||||
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
||||||
attributed_to,
|
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||||
};
|
};
|
||||||
Ok(group)
|
Ok(group)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,18 +49,13 @@ impl InCommunity for BlockUser {
|
||||||
.target
|
.target
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
.dereference(context, local_instance(context).await, request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
let target_community = match target {
|
let community = match target {
|
||||||
SiteOrCommunity::Community(c) => c,
|
SiteOrCommunity::Community(c) => c,
|
||||||
SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()),
|
SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()),
|
||||||
};
|
};
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, target_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(target_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::verify_community_matches,
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{activities::block::block_user::BlockUser, InCommunity},
|
protocol::{activities::block::block_user::BlockUser, InCommunity},
|
||||||
};
|
};
|
||||||
|
@ -35,15 +34,10 @@ impl InCommunity for UndoBlockUser {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> 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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{community::get_community_from_moderators_url, verify_community_matches},
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::InCommunity,
|
protocol::InCommunity,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
||||||
use activitystreams_kinds::activity::AddType;
|
use activitystreams_kinds::activity::AddType;
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_db_schema::source::community::Community;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AddMod {
|
pub struct CollectionAdd {
|
||||||
pub(crate) actor: ObjectId<ApubPerson>,
|
pub(crate) actor: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
pub(crate) object: ObjectId<ApubPerson>,
|
pub(crate) object: Url,
|
||||||
pub(crate) target: Url,
|
pub(crate) target: Url,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub(crate) cc: Vec<Url>,
|
pub(crate) cc: Vec<Url>,
|
||||||
|
@ -28,22 +28,17 @@ pub struct AddMod {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl InCommunity for AddMod {
|
impl InCommunity for CollectionAdd {
|
||||||
async fn community(
|
async fn community(
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let mod_community =
|
let (community, _) =
|
||||||
get_community_from_moderators_url(&self.target, context, request_counter).await?;
|
Community::get_by_collection_url(context.pool(), &self.clone().target.into()).await?;
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, mod_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(mod_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{community::get_community_from_moderators_url, verify_community_matches},
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::InCommunity,
|
protocol::InCommunity,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
use activitypub_federation::{core::object_id::ObjectId, deser::helpers::deserialize_one_or_many};
|
||||||
use activitystreams_kinds::activity::RemoveType;
|
use activitystreams_kinds::activity::RemoveType;
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_db_schema::source::community::Community;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RemoveMod {
|
pub struct CollectionRemove {
|
||||||
pub(crate) actor: ObjectId<ApubPerson>,
|
pub(crate) actor: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
|
@ -28,22 +28,17 @@ pub struct RemoveMod {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl InCommunity for RemoveMod {
|
impl InCommunity for CollectionRemove {
|
||||||
async fn community(
|
async fn community(
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let mod_community =
|
let (community, _) =
|
||||||
get_community_from_moderators_url(&self.target, context, request_counter).await?;
|
Community::get_by_collection_url(context.pool(), &self.clone().target.into()).await?;
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, mod_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(mod_community)
|
|
||||||
}
|
}
|
||||||
|
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 announce;
|
||||||
pub mod remove_mod;
|
pub mod collection_add;
|
||||||
|
pub mod collection_remove;
|
||||||
|
pub mod lock_page;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
||||||
|
@ -8,9 +9,10 @@ pub mod update;
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
activities::community::{
|
activities::community::{
|
||||||
add_mod::AddMod,
|
|
||||||
announce::AnnounceActivity,
|
announce::AnnounceActivity,
|
||||||
remove_mod::RemoveMod,
|
collection_add::CollectionAdd,
|
||||||
|
collection_remove::CollectionRemove,
|
||||||
|
lock_page::{LockPage, UndoLockPage},
|
||||||
report::Report,
|
report::Report,
|
||||||
update::UpdateCommunity,
|
update::UpdateCommunity,
|
||||||
},
|
},
|
||||||
|
@ -24,8 +26,22 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
test_parse_lemmy_item::<AddMod>("assets/lemmy/activities/community/add_mod.json").unwrap();
|
test_parse_lemmy_item::<CollectionAdd>("assets/lemmy/activities/community/add_mod.json")
|
||||||
test_parse_lemmy_item::<RemoveMod>("assets/lemmy/activities/community/remove_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();
|
.unwrap();
|
||||||
|
|
||||||
test_parse_lemmy_item::<UpdateCommunity>(
|
test_parse_lemmy_item::<UpdateCommunity>(
|
||||||
|
|
|
@ -33,17 +33,12 @@ impl InCommunity for Report {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let to_community = self.to[0]
|
let community = self.to[0]
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
.dereference(context, local_instance(context).await, request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, to_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(to_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,17 +36,12 @@ impl InCommunity for UpdateCommunity {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> 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)
|
.dereference(context, local_instance(context).await, request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::verify_community_matches,
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
mentions::MentionOrValue,
|
mentions::MentionOrValue,
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{activities::CreateOrUpdateType, objects::note::Note, InCommunity},
|
protocol::{activities::CreateOrUpdateType, objects::note::Note, InCommunity},
|
||||||
|
@ -37,15 +36,10 @@ impl InCommunity for CreateOrUpdateNote {
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let post = self.object.get_parents(context, request_counter).await?.0;
|
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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
|
Ok(community.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::verify_community_matches,
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{activities::CreateOrUpdateType, objects::page::Page, InCommunity},
|
protocol::{activities::CreateOrUpdateType, objects::page::Page, InCommunity},
|
||||||
};
|
};
|
||||||
|
@ -32,15 +31,10 @@ impl InCommunity for CreateOrUpdatePage {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> 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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{deletion::DeletableObjects, verify_community_matches},
|
activities::{deletion::DeletableObjects, verify_community_matches},
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{objects::tombstone::Tombstone, IdOrNestedObject, InCommunity},
|
protocol::{objects::tombstone::Tombstone, IdOrNestedObject, InCommunity},
|
||||||
};
|
};
|
||||||
|
@ -44,7 +43,7 @@ impl InCommunity for Delete {
|
||||||
async fn community(
|
async fn community(
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
_request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? {
|
let community_id = match DeletableObjects::read_from_db(self.object.id(), context).await? {
|
||||||
DeletableObjects::Community(c) => c.id,
|
DeletableObjects::Community(c) => c.id,
|
||||||
|
@ -57,15 +56,10 @@ impl InCommunity for Delete {
|
||||||
return Err(anyhow!("Private message is not part of community").into())
|
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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
|
Ok(community.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::verify_community_matches,
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{activities::deletion::delete::Delete, InCommunity},
|
protocol::{activities::deletion::delete::Delete, InCommunity},
|
||||||
};
|
};
|
||||||
|
@ -37,15 +36,10 @@ impl InCommunity for UndoDelete {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> 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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance(context).await, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::verify_community_matches,
|
activities::verify_community_matches,
|
||||||
local_instance,
|
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::{activities::voting::vote::Vote, InCommunity},
|
protocol::{activities::voting::vote::Vote, InCommunity},
|
||||||
};
|
};
|
||||||
|
@ -29,16 +28,10 @@ impl InCommunity for UndoVote {
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let local_instance = local_instance(context).await;
|
let community = self.object.community(context, request_counter).await?;
|
||||||
let object_community = self.object.community(context, request_counter).await?;
|
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
Ok(community)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,20 +59,15 @@ impl InCommunity for Vote {
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let local_instance = local_instance(context).await;
|
let local_instance = local_instance(context).await;
|
||||||
let object_community = self
|
let community = self
|
||||||
.object
|
.object
|
||||||
.dereference(context, local_instance, request_counter)
|
.dereference(context, local_instance, request_counter)
|
||||||
.await?
|
.await?
|
||||||
.community(context, request_counter)
|
.community(context, request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, local_instance, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, object_community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(object_community)
|
|
||||||
}
|
}
|
||||||
|
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 empty_outbox;
|
||||||
|
pub(crate) mod group_featured;
|
||||||
pub(crate) mod group_followers;
|
pub(crate) mod group_followers;
|
||||||
pub(crate) mod group_moderators;
|
pub(crate) mod group_moderators;
|
||||||
pub(crate) mod group_outbox;
|
pub(crate) mod group_outbox;
|
||||||
|
@ -8,11 +9,12 @@ mod tests {
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
collections::{
|
collections::{
|
||||||
empty_outbox::EmptyOutbox,
|
empty_outbox::EmptyOutbox,
|
||||||
|
group_featured::GroupFeatured,
|
||||||
group_followers::GroupFollowers,
|
group_followers::GroupFollowers,
|
||||||
group_moderators::GroupModerators,
|
group_moderators::GroupModerators,
|
||||||
group_outbox::GroupOutbox,
|
group_outbox::GroupOutbox,
|
||||||
},
|
},
|
||||||
tests::test_parse_lemmy_item,
|
tests::{test_json, test_parse_lemmy_item},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -22,8 +24,15 @@ mod tests {
|
||||||
let outbox =
|
let outbox =
|
||||||
test_parse_lemmy_item::<GroupOutbox>("assets/lemmy/collections/group_outbox.json").unwrap();
|
test_parse_lemmy_item::<GroupOutbox>("assets/lemmy/collections/group_outbox.json").unwrap();
|
||||||
assert_eq!(outbox.ordered_items.len() as i32, outbox.total_items);
|
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")
|
test_parse_lemmy_item::<GroupModerators>("assets/lemmy/collections/group_moderators.json")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test_parse_lemmy_item::<EmptyOutbox>("assets/lemmy/collections/person_outbox.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::{
|
use crate::{
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
collections::{
|
collections::{
|
||||||
|
community_featured::ApubCommunityFeatured,
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
},
|
},
|
||||||
|
@ -65,6 +66,7 @@ pub struct Group {
|
||||||
pub(crate) posting_restricted_to_mods: Option<bool>,
|
pub(crate) posting_restricted_to_mods: Option<bool>,
|
||||||
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
|
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
|
||||||
pub(crate) endpoints: Option<Endpoints>,
|
pub(crate) endpoints: Option<Endpoints>,
|
||||||
|
pub(crate) featured: Option<ObjectId<ApubCommunityFeatured>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) language: Vec<LanguageTag>,
|
pub(crate) language: Vec<LanguageTag>,
|
||||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||||
|
@ -117,8 +119,10 @@ impl Group {
|
||||||
followers_url: Some(self.followers.into()),
|
followers_url: Some(self.followers.into()),
|
||||||
inbox_url: Some(self.inbox.into()),
|
inbox_url: Some(self.inbox.into()),
|
||||||
shared_inbox_url: self.endpoints.map(|e| e.shared_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,
|
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||||
instance_id,
|
instance_id,
|
||||||
|
featured_url: self.featured.map(Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +150,9 @@ impl Group {
|
||||||
followers_url: Some(self.followers.into()),
|
followers_url: Some(self.followers.into()),
|
||||||
inbox_url: Some(self.inbox.into()),
|
inbox_url: Some(self.inbox.into()),
|
||||||
shared_inbox_url: Some(self.endpoints.map(|e| e.shared_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,
|
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||||
|
featured_url: self.featured.map(Into::into),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,15 +67,11 @@ impl Note {
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
match parent.deref() {
|
match parent.deref() {
|
||||||
PostOrComment::Post(p) => {
|
PostOrComment::Post(p) => Ok((p.clone(), None)),
|
||||||
let post = p.deref().clone();
|
|
||||||
Ok((post, None))
|
|
||||||
}
|
|
||||||
PostOrComment::Comment(c) => {
|
PostOrComment::Comment(c) => {
|
||||||
let post_id = c.post_id;
|
let post_id = c.post_id;
|
||||||
let post = Post::read(context.pool(), post_id).await?;
|
let post = Post::read(context.pool(), post_id).await?;
|
||||||
let comment = c.deref().clone();
|
Ok((post.into(), Some(c.clone())))
|
||||||
Ok((post.into(), Some(comment)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,15 +85,10 @@ impl InCommunity for Note {
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<ApubCommunity, LemmyError> {
|
) -> Result<ApubCommunity, LemmyError> {
|
||||||
let (post, _) = self.get_parents(context, request_counter).await?;
|
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 {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
|
Ok(community.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ pub struct Page {
|
||||||
pub(crate) image: Option<ImageObject>,
|
pub(crate) image: Option<ImageObject>,
|
||||||
pub(crate) comments_enabled: Option<bool>,
|
pub(crate) comments_enabled: Option<bool>,
|
||||||
pub(crate) sensitive: Option<bool>,
|
pub(crate) sensitive: Option<bool>,
|
||||||
|
/// Deprecated, for compatibility with Lemmy 0.17
|
||||||
pub(crate) stickied: Option<bool>,
|
pub(crate) stickied: Option<bool>,
|
||||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||||
|
@ -252,14 +253,9 @@ impl InCommunity for Page {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(audience) = &self.audience {
|
if let Some(audience) = &self.audience {
|
||||||
let audience = audience
|
verify_community_matches(audience, community.actor_id.clone())?;
|
||||||
.dereference(context, instance, request_counter)
|
|
||||||
.await?;
|
|
||||||
verify_community_matches(&audience, community.id)?;
|
|
||||||
Ok(audience)
|
|
||||||
} else {
|
|
||||||
Ok(community)
|
|
||||||
}
|
}
|
||||||
|
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 {
|
impl CommunityModerator {
|
||||||
pub async fn delete_for_community(
|
pub async fn delete_for_community(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
|
@ -430,6 +462,8 @@ mod tests {
|
||||||
followers_url: inserted_community.followers_url.clone(),
|
followers_url: inserted_community.followers_url.clone(),
|
||||||
inbox_url: inserted_community.inbox_url.clone(),
|
inbox_url: inserted_community.inbox_url.clone(),
|
||||||
shared_inbox_url: None,
|
shared_inbox_url: None,
|
||||||
|
moderators_url: None,
|
||||||
|
featured_url: None,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
posting_restricted_to_mods: false,
|
posting_restricted_to_mods: false,
|
||||||
instance_id: inserted_instance.id,
|
instance_id: inserted_instance.id,
|
||||||
|
|
|
@ -89,6 +89,22 @@ impl Post {
|
||||||
.await
|
.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(
|
pub async fn permadelete_for_creator(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
for_creator_id: PersonId,
|
for_creator_id: PersonId,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
use diesel_ltree::Ltree;
|
use diesel_ltree::Ltree;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -110,7 +112,7 @@ pub struct LocalSiteId(i32);
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
||||||
#[cfg_attr(feature = "full", diesel(sql_type = diesel::sql_types::Text))]
|
#[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")]
|
#[cfg(feature = "full")]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -128,13 +130,23 @@ impl Display for DbUrl {
|
||||||
#[allow(clippy::from_over_into)]
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<DbUrl> for Url {
|
impl Into<DbUrl> for Url {
|
||||||
fn into(self) -> DbUrl {
|
fn into(self) -> DbUrl {
|
||||||
DbUrl(self)
|
DbUrl(Box::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::from_over_into)]
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<Url> for DbUrl {
|
impl Into<Url> for DbUrl {
|
||||||
fn into(self) -> Url {
|
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,
|
followers_url -> Varchar,
|
||||||
inbox_url -> Varchar,
|
inbox_url -> Varchar,
|
||||||
shared_inbox_url -> Nullable<Varchar>,
|
shared_inbox_url -> Nullable<Varchar>,
|
||||||
|
moderators_url -> Nullable<Varchar>,
|
||||||
|
featured_url -> Nullable<Varchar>,
|
||||||
hidden -> Bool,
|
hidden -> Bool,
|
||||||
posting_restricted_to_mods -> Bool,
|
posting_restricted_to_mods -> Bool,
|
||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
|
|
|
@ -27,6 +27,12 @@ pub struct Community {
|
||||||
pub followers_url: DbUrl,
|
pub followers_url: DbUrl,
|
||||||
pub inbox_url: DbUrl,
|
pub inbox_url: DbUrl,
|
||||||
pub shared_inbox_url: Option<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 hidden: bool,
|
||||||
pub posting_restricted_to_mods: bool,
|
pub posting_restricted_to_mods: bool,
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
|
@ -80,6 +86,8 @@ pub struct CommunityInsertForm {
|
||||||
pub followers_url: Option<DbUrl>,
|
pub followers_url: Option<DbUrl>,
|
||||||
pub inbox_url: Option<DbUrl>,
|
pub inbox_url: Option<DbUrl>,
|
||||||
pub shared_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 hidden: Option<bool>,
|
||||||
pub posting_restricted_to_mods: Option<bool>,
|
pub posting_restricted_to_mods: Option<bool>,
|
||||||
#[builder(!default)]
|
#[builder(!default)]
|
||||||
|
@ -108,6 +116,8 @@ pub struct CommunityUpdateForm {
|
||||||
pub followers_url: Option<DbUrl>,
|
pub followers_url: Option<DbUrl>,
|
||||||
pub inbox_url: Option<DbUrl>,
|
pub inbox_url: Option<DbUrl>,
|
||||||
pub shared_inbox_url: Option<Option<DbUrl>>,
|
pub shared_inbox_url: Option<Option<DbUrl>>,
|
||||||
|
pub moderators_url: Option<DbUrl>,
|
||||||
|
pub featured_url: Option<DbUrl>,
|
||||||
pub hidden: Option<bool>,
|
pub hidden: Option<bool>,
|
||||||
pub posting_restricted_to_mods: 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> {
|
fn from_sql(value: diesel::backend::RawValue<'_, DB>) -> diesel::deserialize::Result<Self> {
|
||||||
let str = String::from_sql(value)?;
|
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>,
|
for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||||
{
|
{
|
||||||
fn from(id: ObjectId<Kind>) -> Self {
|
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"
|
- 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:
|
depends_on:
|
||||||
- postgres_alpha
|
- postgres_alpha
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8541:8541"
|
- "8541:8541"
|
||||||
postgres_alpha:
|
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"
|
- 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:
|
depends_on:
|
||||||
- postgres_beta
|
- postgres_beta
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8551:8551"
|
- "8551:8551"
|
||||||
postgres_beta:
|
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"
|
- 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:
|
depends_on:
|
||||||
- postgres_gamma
|
- postgres_gamma
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8561:8561"
|
- "8561:8561"
|
||||||
postgres_gamma:
|
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"
|
- 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:
|
depends_on:
|
||||||
- postgres_delta
|
- postgres_delta
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8571:8571"
|
- "8571:8571"
|
||||||
postgres_delta:
|
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"
|
- 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:
|
depends_on:
|
||||||
- postgres_epsilon
|
- postgres_epsilon
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "8581:8581"
|
- "8581:8581"
|
||||||
postgres_epsilon:
|
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