Allow adding remote users as community mods (ref #1061)
This commit is contained in:
parent
dcf40db225
commit
3ffae1f5b8
7 changed files with 121 additions and 23 deletions
|
@ -10,6 +10,7 @@ use actix_web::web::Data;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use lemmy_api_structs::{blocking, community::*};
|
use lemmy_api_structs::{blocking, community::*};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
|
activities::send::community::{send_add_mod, send_remove_mod},
|
||||||
generate_apub_endpoint,
|
generate_apub_endpoint,
|
||||||
generate_followers_url,
|
generate_followers_url,
|
||||||
generate_inbox_url,
|
generate_inbox_url,
|
||||||
|
@ -34,7 +35,7 @@ use lemmy_db_queries::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
naive_now,
|
naive_now,
|
||||||
source::{comment::Comment, community::*, moderator::*, post::Post, site::*},
|
source::{comment::Comment, community::*, moderator::*, post::Post, site::*, user::User_},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
|
@ -699,16 +700,16 @@ impl Perform for AddModToCommunity {
|
||||||
let data: &AddModToCommunity = &self;
|
let data: &AddModToCommunity = &self;
|
||||||
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
let user = get_user_from_jwt(&data.auth, context.pool()).await?;
|
||||||
|
|
||||||
let community_moderator_form = CommunityModeratorForm {
|
|
||||||
community_id: data.community_id,
|
|
||||||
user_id: data.user_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
||||||
// Verify that only mods or admins can add mod
|
// Verify that only mods or admins can add mod
|
||||||
is_mod_or_admin(context.pool(), user.id, community_id).await?;
|
is_mod_or_admin(context.pool(), user.id, community_id).await?;
|
||||||
|
|
||||||
|
// Update in local database
|
||||||
|
let community_moderator_form = CommunityModeratorForm {
|
||||||
|
community_id: data.community_id,
|
||||||
|
user_id: data.user_id,
|
||||||
|
};
|
||||||
if data.added {
|
if data.added {
|
||||||
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
|
||||||
if blocking(context.pool(), join).await?.is_err() {
|
if blocking(context.pool(), join).await?.is_err() {
|
||||||
|
@ -733,6 +734,25 @@ impl Perform for AddModToCommunity {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
// Send to federated instances
|
||||||
|
let updated_mod_id = data.user_id;
|
||||||
|
let updated_mod = blocking(context.pool(), move |conn| {
|
||||||
|
User_::read(conn, updated_mod_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
let community = blocking(context.pool(), move |conn| {
|
||||||
|
Community::read(conn, community_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
dbg!(data.added);
|
||||||
|
if data.added {
|
||||||
|
send_add_mod(user, updated_mod, community, context).await?;
|
||||||
|
} else {
|
||||||
|
send_remove_mod(user, updated_mod, community, context).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
|
||||||
|
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let moderators = blocking(context.pool(), move |conn| {
|
let moderators = blocking(context.pool(), move |conn| {
|
||||||
CommunityModeratorView::for_community(conn, community_id)
|
CommunityModeratorView::for_community(conn, community_id)
|
||||||
|
@ -740,18 +760,18 @@ impl Perform for AddModToCommunity {
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let res = AddModToCommunityResponse { moderators };
|
let res = AddModToCommunityResponse { moderators };
|
||||||
|
|
||||||
context.chat_server().do_send(SendCommunityRoomMessage {
|
context.chat_server().do_send(SendCommunityRoomMessage {
|
||||||
op: UserOperation::AddModToCommunity,
|
op: UserOperation::AddModToCommunity,
|
||||||
response: res.clone(),
|
response: res.clone(),
|
||||||
community_id,
|
community_id,
|
||||||
websocket_id,
|
websocket_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: we dont do anything for federation here, it should be updated the next time the community
|
||||||
|
// gets fetched. i hope we can get rid of the community creator role soon.
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for TransferCommunity {
|
impl Perform for TransferCommunity {
|
||||||
type Response = GetCommunityResponse;
|
type Response = GetCommunityResponse;
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub(crate) mod receive;
|
pub(crate) mod receive;
|
||||||
pub(crate) mod send;
|
pub mod send;
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::send::generate_activity_id,
|
activities::send::generate_activity_id,
|
||||||
activity_queue::{send_activity_single_dest, send_to_community_followers},
|
activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
|
||||||
check_is_apub_id_valid,
|
check_is_apub_id_valid,
|
||||||
extensions::context::lemmy_context,
|
extensions::context::lemmy_context,
|
||||||
fetcher::user::get_or_fetch_and_upsert_user,
|
fetcher::user::get_or_fetch_and_upsert_user,
|
||||||
|
generate_moderators_url,
|
||||||
ActorType,
|
ActorType,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
activity::{
|
activity::{
|
||||||
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
kind::{AcceptType, AddType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
|
||||||
Accept,
|
Accept,
|
||||||
ActorAndObjectRefExt,
|
ActorAndObjectRefExt,
|
||||||
|
Add,
|
||||||
Announce,
|
Announce,
|
||||||
Delete,
|
Delete,
|
||||||
Follow,
|
Follow,
|
||||||
|
OptTargetRefExt,
|
||||||
Remove,
|
Remove,
|
||||||
Undo,
|
Undo,
|
||||||
},
|
},
|
||||||
|
@ -25,7 +28,7 @@ use anyhow::Context;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_structs::blocking;
|
||||||
use lemmy_db_queries::DbPool;
|
use lemmy_db_queries::DbPool;
|
||||||
use lemmy_db_schema::source::community::Community;
|
use lemmy_db_schema::source::{community::Community, user::User_};
|
||||||
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||||
use lemmy_utils::{location_info, LemmyError};
|
use lemmy_utils::{location_info, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
@ -202,3 +205,55 @@ impl ActorType for Community {
|
||||||
Ok(inboxes)
|
Ok(inboxes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_add_mod(
|
||||||
|
actor: User_,
|
||||||
|
added_mod: User_,
|
||||||
|
community: Community,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let mut add = Add::new(
|
||||||
|
actor.actor_id.clone().into_inner(),
|
||||||
|
added_mod.actor_id.into_inner(),
|
||||||
|
);
|
||||||
|
add
|
||||||
|
.set_many_contexts(lemmy_context()?)
|
||||||
|
.set_id(generate_activity_id(AddType::Add)?)
|
||||||
|
.set_many_tos(vec![community.actor_id.to_owned().into_inner(), public()])
|
||||||
|
.set_target(generate_moderators_url(&community.actor_id)?.into_inner());
|
||||||
|
|
||||||
|
if community.local {
|
||||||
|
community
|
||||||
|
.send_announce(add.into_any_base()?, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
send_to_community(add, &actor, &community, context).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_remove_mod(
|
||||||
|
actor: User_,
|
||||||
|
removed_mod: User_,
|
||||||
|
community: Community,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let mut remove = Remove::new(
|
||||||
|
actor.actor_id.clone().into_inner(),
|
||||||
|
removed_mod.actor_id.into_inner(),
|
||||||
|
);
|
||||||
|
remove
|
||||||
|
.set_many_contexts(lemmy_context()?)
|
||||||
|
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||||
|
.set_many_tos(vec![community.actor_id.to_owned().into_inner(), public()])
|
||||||
|
.set_target(generate_moderators_url(&community.actor_id)?.into_inner());
|
||||||
|
|
||||||
|
if community.local {
|
||||||
|
community
|
||||||
|
.send_announce(remove.into_any_base()?, context)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
send_to_community(remove, &actor, &community, context).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use url::{ParseError, Url};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(crate) mod comment;
|
pub(crate) mod comment;
|
||||||
pub(crate) mod community;
|
pub mod community;
|
||||||
pub(crate) mod post;
|
pub(crate) mod post;
|
||||||
pub(crate) mod private_message;
|
pub(crate) mod private_message;
|
||||||
pub(crate) mod user;
|
pub(crate) mod user;
|
||||||
|
|
|
@ -37,6 +37,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
find_post_or_comment_by_id,
|
find_post_or_comment_by_id,
|
||||||
inbox::is_addressed_to_public,
|
inbox::is_addressed_to_public,
|
||||||
|
ActorType,
|
||||||
PostOrComment,
|
PostOrComment,
|
||||||
};
|
};
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
|
@ -58,7 +59,7 @@ use activitystreams::{
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use diesel::result::Error::NotFound;
|
use diesel::result::Error::NotFound;
|
||||||
use lemmy_api_structs::blocking;
|
use lemmy_api_structs::blocking;
|
||||||
use lemmy_db_queries::{ApubObject, Crud, Joinable};
|
use lemmy_db_queries::{source::community::CommunityModerator_, ApubObject, Crud, Joinable};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
|
@ -213,7 +214,7 @@ pub(in crate::inbox) async fn receive_remove_for_community(
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let remove = Remove::from_any_base(activity)?.context(location_info!())?;
|
let remove = Remove::from_any_base(activity.to_owned())?.context(location_info!())?;
|
||||||
verify_activity_domains_valid(&remove, &expected_domain, false)?;
|
verify_activity_domains_valid(&remove, &expected_domain, false)?;
|
||||||
is_addressed_to_public(&remove)?;
|
is_addressed_to_public(&remove)?;
|
||||||
|
|
||||||
|
@ -234,6 +235,8 @@ pub(in crate::inbox) async fn receive_remove_for_community(
|
||||||
CommunityModerator::leave(conn, &form)
|
CommunityModerator::leave(conn, &form)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
community.send_announce(activity, context).await?;
|
||||||
|
// TODO: send websocket notification about removed mod
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// Remove a post or comment
|
// Remove a post or comment
|
||||||
|
@ -385,7 +388,7 @@ pub(in crate::inbox) async fn receive_add_for_community(
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
request_counter: &mut i32,
|
request_counter: &mut i32,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let add = Add::from_any_base(activity)?.context(location_info!())?;
|
let add = Add::from_any_base(activity.to_owned())?.context(location_info!())?;
|
||||||
verify_activity_domains_valid(&add, &expected_domain, false)?;
|
verify_activity_domains_valid(&add, &expected_domain, false)?;
|
||||||
is_addressed_to_public(&add)?;
|
is_addressed_to_public(&add)?;
|
||||||
let community = verify_actor_is_community_mod(&add, context).await?;
|
let community = verify_actor_is_community_mod(&add, context).await?;
|
||||||
|
@ -395,14 +398,28 @@ pub(in crate::inbox) async fn receive_add_for_community(
|
||||||
.as_single_xsd_any_uri()
|
.as_single_xsd_any_uri()
|
||||||
.context(location_info!())?;
|
.context(location_info!())?;
|
||||||
let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?;
|
let new_mod = get_or_fetch_and_upsert_user(&new_mod, context, request_counter).await?;
|
||||||
let form = CommunityModeratorForm {
|
|
||||||
community_id: community.id,
|
// If we had to refetch the community while parsing the activity, then the new mod has already
|
||||||
user_id: new_mod.id,
|
// been added. Skip it here as it would result in a duplicate key error.
|
||||||
};
|
let new_mod_id = new_mod.id;
|
||||||
blocking(context.pool(), move |conn| {
|
let moderated_communities = blocking(context.pool(), move |conn| {
|
||||||
CommunityModerator::join(conn, &form)
|
CommunityModerator::get_user_moderated_communities(conn, new_mod_id)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
if moderated_communities.contains(&community.id) {
|
||||||
|
let form = CommunityModeratorForm {
|
||||||
|
community_id: community.id,
|
||||||
|
user_id: new_mod.id,
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
CommunityModerator::join(conn, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
}
|
||||||
|
if community.local {
|
||||||
|
community.send_announce(activity, context).await?;
|
||||||
|
}
|
||||||
|
// TODO: send websocket notification about added mod
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +475,7 @@ where
|
||||||
// should be the moderators collection of a local community
|
// should be the moderators collection of a local community
|
||||||
let target = activity
|
let target = activity
|
||||||
.target()
|
.target()
|
||||||
.map(|t| t.as_single_xsd_string())
|
.map(|t| t.as_single_xsd_any_uri())
|
||||||
.flatten()
|
.flatten()
|
||||||
.context(location_info!())?;
|
.context(location_info!())?;
|
||||||
// TODO: very hacky, we should probably store the moderators url in db
|
// TODO: very hacky, we should probably store the moderators url in db
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub enum ValidTypes {
|
||||||
Undo,
|
Undo,
|
||||||
Remove,
|
Remove,
|
||||||
Announce,
|
Announce,
|
||||||
|
Add,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
|
// TODO: this isnt entirely correct, cause some of these receive are not ActorAndObject,
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
||||||
is_addressed_to_local_user,
|
is_addressed_to_local_user,
|
||||||
is_addressed_to_public,
|
is_addressed_to_public,
|
||||||
receive_for_community::{
|
receive_for_community::{
|
||||||
|
receive_add_for_community,
|
||||||
receive_create_for_community,
|
receive_create_for_community,
|
||||||
receive_delete_for_community,
|
receive_delete_for_community,
|
||||||
receive_dislike_for_community,
|
receive_dislike_for_community,
|
||||||
|
@ -252,6 +253,7 @@ enum AnnouncableActivities {
|
||||||
Delete,
|
Delete,
|
||||||
Remove,
|
Remove,
|
||||||
Undo,
|
Undo,
|
||||||
|
Add,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes an announce and passes the inner activity to the appropriate handler.
|
/// Takes an announce and passes the inner activity to the appropriate handler.
|
||||||
|
@ -302,6 +304,9 @@ pub async fn receive_announce(
|
||||||
Some(Undo) => {
|
Some(Undo) => {
|
||||||
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
|
receive_undo_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||||
}
|
}
|
||||||
|
Some(Add) => {
|
||||||
|
receive_add_for_community(context, inner_activity, &inner_id, request_counter).await
|
||||||
|
}
|
||||||
_ => receive_unhandled_activity(inner_activity),
|
_ => receive_unhandled_activity(inner_activity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue