Rewrite remaining federation actions, get rid of PerformCrud trait (#3794)

* Rewrite ban actions

* Rewrite delete/remove actions

* Rewrite remove/delete community

* Rewrite report actions

* Rewrite feature/lock post

* Rewrite update community actions

* Rewrite remaining federation actions

* Get rid of PerformCrud trait

* clippy
This commit is contained in:
Nutomic 2023-08-02 18:52:41 +02:00 committed by GitHub
parent be1389420b
commit 27be1efb74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1699 additions and 1927 deletions

View file

@ -30,9 +30,6 @@ else
done done
fi fi
echo "killall existing lemmy_server processes"
killall -s1 lemmy_server || true
echo "$PWD" echo "$PWD"
echo "start alpha" echo "start alpha"

View file

@ -7,12 +7,14 @@ pushd ..
cargo build cargo build
rm target/lemmy_server || true rm target/lemmy_server || true
cp target/debug/lemmy_server target/lemmy_server cp target/debug/lemmy_server target/lemmy_server
killall -s1 lemmy_server || true
./api_tests/prepare-drone-federation-test.sh ./api_tests/prepare-drone-federation-test.sh
popd popd
yarn yarn
yarn api-test || true yarn api-test || true
killall -s1 lemmy_server || true
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE" psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE"
done done

View file

@ -1,8 +1,10 @@
use crate::{check_report_reason, Perform}; use crate::check_report_reason;
use actix_web::web::Data; use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
comment::{CommentReportResponse, CreateCommentReport}, comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -21,55 +23,60 @@ use lemmy_db_views::structs::{CommentReportView, CommentView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
/// Creates a comment report and notifies the moderators of the community /// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreateCommentReport { pub async fn create_comment_report(
type Response = CommentReportResponse; data: Json<CreateCommentReport>,
context: Data<LemmyContext>,
) -> Result<Json<CommentReportResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let reason = sanitize_html(data.reason.trim());
async fn perform( check_report_reason(&reason, &local_site)?;
&self,
context: &Data<LemmyContext>,
) -> Result<CommentReportResponse, LemmyError> {
let data: &CreateCommentReport = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = sanitize_html(self.reason.trim()); let person_id = local_user_view.person.id;
check_report_reason(&reason, &local_site)?; let comment_id = data.comment_id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
let person_id = local_user_view.person.id; check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?;
let comment_id = data.comment_id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?; let report_form = CommentReportForm {
creator_id: person_id,
comment_id,
original_comment_text: comment_view.comment.content,
reason,
};
let report_form = CommentReportForm { let report = CommentReport::report(&mut context.pool(), &report_form)
creator_id: person_id, .await
comment_id, .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
original_comment_text: comment_view.comment.content,
reason,
};
let report = CommentReport::report(&mut context.pool(), &report_form) let comment_report_view =
.await CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let comment_report_view = // Email the admins
CommentReportView::read(&mut context.pool(), report.id, person_id).await?; if local_site.reports_email_admins {
send_new_report_email_to_admins(
// Email the admins &comment_report_view.creator.name,
if local_site.reports_email_admins { &comment_report_view.comment_creator.name,
send_new_report_email_to_admins( &mut context.pool(),
&comment_report_view.creator.name, context.settings(),
&comment_report_view.comment_creator.name, )
&mut context.pool(), .await?;
context.settings(),
)
.await?;
}
Ok(CommentReportResponse {
comment_report_view,
})
} }
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
comment_view.comment.ap_id.inner().clone(),
local_user_view.person,
comment_view.community,
data.reason.clone(),
),
&context,
)
.await?;
Ok(Json(CommentReportResponse {
comment_report_view,
}))
} }

View file

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse}, community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_mod_or_admin, local_user_view_from_jwt}, utils::{is_mod_or_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,58 +16,62 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for AddModToCommunity { pub async fn add_mod_to_community(
type Response = AddModToCommunityResponse; data: Json<AddModToCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<AddModToCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let community_id = data.community_id;
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; // Verify that only mods or admins can add mod
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?;
// Verify that only mods or admins can add mod let community = Community::read(&mut context.pool(), community_id).await?;
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?; if local_user_view.person.admin && !community.local {
let community = Community::read(&mut context.pool(), community_id).await?; return Err(LemmyErrorType::NotAModerator)?;
if local_user_view.person.admin && !community.local {
return Err(LemmyErrorType::NotAModerator)?;
}
// Update in local database
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
person_id: data.person_id,
};
if data.added {
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
} else {
CommunityModerator::leave(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
}
// Mod tables
let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
removed: Some(!data.added),
};
ModAddCommunity::create(&mut context.pool(), &form).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 moderators =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
Ok(AddModToCommunityResponse { moderators })
} }
// Update in local database
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
person_id: data.person_id,
};
if data.added {
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
} else {
CommunityModerator::leave(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
}
// Mod tables
let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
removed: Some(!data.added),
};
ModAddCommunity::create(&mut context.pool(), &form).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 moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
ActivityChannel::submit_activity(
SendActivityData::AddModToCommunity(
local_user_view.person,
data.community_id,
data.person_id,
data.added,
),
&context,
)
.await?;
Ok(Json(AddModToCommunityResponse { moderators }))
} }

View file

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse}, community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
is_mod_or_admin, is_mod_or_admin,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -28,77 +29,85 @@ use lemmy_utils::{
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::{time::naive_from_unix, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for BanFromCommunity { pub async fn ban_from_community(
type Response = BanFromCommunityResponse; data: Json<BanFromCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let banned_person_id = data.person_id;
async fn perform( let remove_data = data.remove_data.unwrap_or(false);
&self, let expires = data.expires.map(naive_from_unix);
context: &Data<LemmyContext>,
) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; // Verify that only mods or admins can ban
let banned_person_id = data.person_id; is_mod_or_admin(
let remove_data = data.remove_data.unwrap_or(false); &mut context.pool(),
let expires = data.expires.map(naive_from_unix); local_user_view.person.id,
data.community_id,
)
.await?;
is_valid_body_field(&data.reason, false)?;
// Verify that only mods or admins can ban let community_user_ban_form = CommunityPersonBanForm {
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?; community_id: data.community_id,
is_valid_body_field(&data.reason, false)?; person_id: data.person_id,
expires: Some(expires),
};
let community_user_ban_form = CommunityPersonBanForm { if data.ban {
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, person_id: banned_person_id,
expires: Some(expires), pending: false,
}; };
if data.ban { CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form) .await
.await .ok();
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; } else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
// Also unsubscribe them from the community, if they are subscribed .await
let community_follower_form = CommunityFollowerForm { .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
community_id: data.community_id,
person_id: banned_person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
}
// Remove/Restore their data if that's desired
if remove_data {
remove_user_data_in_community(community_id, banned_person_id, &mut context.pool()).await?;
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_id = data.person_id;
let person_view = PersonView::read(&mut context.pool(), person_id).await?;
Ok(BanFromCommunityResponse {
person_view,
banned: data.ban,
})
} }
// Remove/Restore their data if that's desired
if remove_data {
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?;
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity(
local_user_view.person,
data.community_id,
person_view.person.clone(),
data.0.clone(),
),
&context,
)
.await?;
Ok(Json(BanFromCommunityResponse {
person_view,
banned: data.ban,
}))
} }

View file

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{BlockCommunity, BlockCommunityResponse}, community::{BlockCommunity, BlockCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,52 +16,56 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityView; use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for BlockCommunity { pub async fn block_community(
type Response = BlockCommunityResponse; data: Json<BlockCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<BlockCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let community_id = data.community_id;
async fn perform( let person_id = local_user_view.person.id;
&self, let community_block_form = CommunityBlockForm {
context: &Data<LemmyContext>, person_id,
) -> Result<BlockCommunityResponse, LemmyError> { community_id,
let data: &BlockCommunity = self; };
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; if data.block {
let person_id = local_user_view.person.id; CommunityBlock::block(&mut context.pool(), &community_block_form)
let community_block_form = CommunityBlockForm { .await
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
// Also, unfollow the community, and send a federated unfollow
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
person_id, person_id,
community_id, pending: false,
}; };
if data.block { CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
CommunityBlock::block(&mut context.pool(), &community_block_form) .await
.await .ok();
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; } else {
CommunityBlock::unblock(&mut context.pool(), &community_block_form)
// Also, unfollow the community, and send a federated unfollow .await
let community_follower_form = CommunityFollowerForm { .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
community_id: data.community_id,
person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityBlock::unblock(&mut context.pool(), &community_block_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
}
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
Ok(BlockCommunityResponse {
blocked: data.block,
community_view,
})
} }
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(
community_view.community.clone(),
local_user_view.person.clone(),
false,
),
&context,
)
.await?;
Ok(Json(BlockCommunityResponse {
blocked: data.block,
community_view,
}))
} }

View file

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, HideCommunity}, community::{CommunityResponse, HideCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt}, utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,36 +16,38 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for HideCommunity { pub async fn hide_community(
type Response = CommunityResponse; data: Json<HideCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
// Verify its a admin (only admin can hide or unhide it)
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
is_admin(&local_user_view)?;
#[tracing::instrument(skip(context))] let community_form = CommunityUpdateForm::builder()
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { .hidden(Some(data.hidden))
let data: &HideCommunity = self; .build();
// Verify its a admin (only admin can hide or unhide it) let mod_hide_community_form = ModHideCommunityForm {
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; community_id: data.community_id,
is_admin(&local_user_view)?; mod_person_id: local_user_view.person.id,
reason: sanitize_html_opt(&data.reason),
hidden: Some(data.hidden),
};
let community_form = CommunityUpdateForm::builder() let community_id = data.community_id;
.hidden(Some(data.hidden)) let community = Community::update(&mut context.pool(), community_id, &community_form)
.build(); .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?;
let mod_hide_community_form = ModHideCommunityForm { ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?;
community_id: data.community_id,
mod_person_id: local_user_view.person.id,
reason: sanitize_html_opt(&data.reason),
hidden: Some(data.hidden),
};
let community_id = data.community_id; ActivityChannel::submit_activity(
Community::update(&mut context.pool(), community_id, &community_form) SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
.await &context,
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?; )
.await?;
ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?; build_community_response(&context, local_user_view, community_id).await
build_community_response(context, local_user_view, community_id).await
}
} }

View file

@ -1,6 +1,6 @@
mod add_mod; pub mod add_mod;
mod ban; pub mod ban;
mod block; pub mod block;
pub mod follow; pub mod follow;
mod hide; pub mod hide;
mod transfer; pub mod transfer;

View file

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{BanPerson, BanPersonResponse}, person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt}, utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -17,65 +18,68 @@ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::{time::naive_from_unix, validation::is_valid_body_field},
}; };
#[tracing::instrument(skip(context))]
pub async fn ban_from_site(
data: Json<BanPerson>,
context: Data<LemmyContext>,
) -> Result<Json<BanPersonResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[async_trait::async_trait(?Send)] // Make sure user is an admin
impl Perform for BanPerson { is_admin(&local_user_view)?;
type Response = BanPersonResponse;
#[tracing::instrument(skip(context))] is_valid_body_field(&data.reason, false)?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<BanPersonResponse, LemmyError> {
let data: &BanPerson = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Make sure user is an admin let expires = data.expires.map(naive_from_unix);
is_admin(&local_user_view)?;
is_valid_body_field(&data.reason, false)?; let person = Person::update(
&mut context.pool(),
data.person_id,
&PersonUpdateForm::builder()
.banned(Some(data.ban))
.ban_expires(Some(expires))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
let ban = data.ban; // Remove their data if that's desired
let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false);
let expires = data.expires.map(naive_from_unix); if remove_data {
remove_user_data(
let person = Person::update( person.id,
&mut context.pool(), &mut context.pool(),
banned_person_id, context.settings(),
&PersonUpdateForm::builder() context.client(),
.banned(Some(ban))
.ban_expires(Some(expires))
.build(),
) )
.await .await?;
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
if remove_data {
remove_user_data(
person.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
}
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBan::create(&mut context.pool(), &form).await?;
let person_id = data.person_id;
let person_view = PersonView::read(&mut context.pool(), person_id).await?;
Ok(BanPersonResponse {
person_view,
banned: data.ban,
})
} }
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBan::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromSite(
local_user_view.person,
person_view.person.clone(),
data.0.clone(),
),
&context,
)
.await?;
Ok(Json(BanPersonResponse {
person_view,
banned: data.ban,
}))
} }

View file

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{FeaturePost, PostResponse}, post::{FeaturePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -22,67 +23,65 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for FeaturePost { pub async fn feature_post(
type Response = PostResponse; data: Json<FeaturePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &FeaturePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
check_community_ban( if data.feature_type == PostFeatureType::Community {
// Verify that only the mods can feature in community
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id, local_user_view.person.id,
orig_post.community_id, orig_post.community_id,
&mut context.pool(),
) )
.await?; .await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; } else {
is_admin(&local_user_view)?;
if data.feature_type == PostFeatureType::Community {
// Verify that only the mods can feature in community
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
} else {
is_admin(&local_user_view)?;
}
// Update the post
let post_id = data.post_id;
let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community {
PostUpdateForm::builder()
.featured_community(Some(data.featured))
.build()
} else {
PostUpdateForm::builder()
.featured_local(Some(data.featured))
.build()
};
Post::update(&mut context.pool(), post_id, &new_post).await?;
// Mod tables
let form = ModFeaturePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
featured: data.featured,
is_featured_community: data.feature_type == PostFeatureType::Community,
};
ModFeaturePost::create(&mut context.pool(), &form).await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
// Update the post
let post_id = data.post_id;
let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community {
PostUpdateForm::builder()
.featured_community(Some(data.featured))
.build()
} else {
PostUpdateForm::builder()
.featured_local(Some(data.featured))
.build()
};
let post = Post::update(&mut context.pool(), post_id, &new_post).await?;
// Mod tables
let form = ModFeaturePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
featured: data.featured,
is_featured_community: data.feature_type == PostFeatureType::Community,
};
ModFeaturePost::create(&mut context.pool(), &form).await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::FeaturePost(post, local_user_view.person, data.featured),
&context,
)
.await?;
build_post_response(&context, orig_post.community_id, person_id, post_id).await
} }

View file

@ -80,13 +80,11 @@ pub async fn like_post(
) )
.await?; .await?;
Ok(Json( build_post_response(
build_post_response( context.deref(),
context.deref(), post.community_id,
post.community_id, local_user_view.person.id,
local_user_view.person.id, post_id,
post_id, )
) .await
.await?,
))
} }

View file

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{LockPost, PostResponse}, post::{LockPost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -20,58 +21,56 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for LockPost { pub async fn lock_post(
type Response = PostResponse; data: Json<LockPost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &LockPost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
check_community_ban( // Verify that only the mods can lock
local_user_view.person.id, is_mod_or_admin(
orig_post.community_id, &mut context.pool(),
&mut context.pool(), local_user_view.person.id,
) orig_post.community_id,
.await?; )
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; .await?;
// Verify that only the mods can lock // Update the post
is_mod_or_admin( let post_id = data.post_id;
&mut context.pool(), let locked = data.locked;
local_user_view.person.id, let post = Post::update(
orig_post.community_id, &mut context.pool(),
) post_id,
.await?; &PostUpdateForm::builder().locked(Some(locked)).build(),
)
.await?;
// Update the post // Mod tables
let post_id = data.post_id; let form = ModLockPostForm {
let locked = data.locked; mod_person_id: local_user_view.person.id,
Post::update( post_id: data.post_id,
&mut context.pool(), locked: Some(locked),
post_id, };
&PostUpdateForm::builder().locked(Some(locked)).build(), ModLockPost::create(&mut context.pool(), &form).await?;
)
.await?;
// Mod tables let person_id = local_user_view.person.id;
let form = ModLockPostForm { ActivityChannel::submit_activity(
mod_person_id: local_user_view.person.id, SendActivityData::LockPost(post, local_user_view.person, data.locked),
post_id: data.post_id, &context,
locked: Some(locked), )
}; .await?;
ModLockPost::create(&mut context.pool(), &form).await?;
build_post_response( build_post_response(&context, orig_post.community_id, person_id, post_id).await
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
} }

View file

@ -1,8 +1,10 @@
use crate::{check_report_reason, Perform}; use crate::check_report_reason;
use actix_web::web::Data; use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{CreatePostReport, PostReportResponse}, post::{CreatePostReport, PostReportResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -21,51 +23,59 @@ use lemmy_db_views::structs::{PostReportView, PostView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
/// Creates a post report and notifies the moderators of the community /// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreatePostReport { pub async fn create_post_report(
type Response = PostReportResponse; data: Json<CreatePostReport>,
context: Data<LemmyContext>,
) -> Result<Json<PostReportResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let reason = sanitize_html(data.reason.trim());
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostReportResponse, LemmyError> { check_report_reason(&reason, &local_site)?;
let data: &CreatePostReport = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = sanitize_html(self.reason.trim()); let person_id = local_user_view.person.id;
check_report_reason(&reason, &local_site)?; let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, None).await?;
let person_id = local_user_view.person.id; check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?;
let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, None).await?;
check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?; let report_form = PostReportForm {
creator_id: person_id,
post_id,
original_post_name: post_view.post.name,
original_post_url: post_view.post.url,
original_post_body: post_view.post.body,
reason,
};
let report_form = PostReportForm { let report = PostReport::report(&mut context.pool(), &report_form)
creator_id: person_id, .await
post_id, .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
original_post_name: post_view.post.name,
original_post_url: post_view.post.url,
original_post_body: post_view.post.body,
reason,
};
let report = PostReport::report(&mut context.pool(), &report_form) let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?; // Email the admins
if local_site.reports_email_admins {
// Email the admins send_new_report_email_to_admins(
if local_site.reports_email_admins { &post_report_view.creator.name,
send_new_report_email_to_admins( &post_report_view.post_creator.name,
&post_report_view.creator.name, &mut context.pool(),
&post_report_view.post_creator.name, context.settings(),
&mut context.pool(), )
context.settings(), .await?;
)
.await?;
}
Ok(PostReportResponse { post_report_view })
} }
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
post_view.post.ap_id.inner().clone(),
local_user_view.person,
post_view.community,
data.reason.clone(),
),
&context,
)
.await?;
Ok(Json(PostReportResponse { post_report_view }))
} }

View file

@ -1,3 +1,3 @@
mod create; pub mod create;
mod list; pub mod list;
mod resolve; pub mod resolve;

View file

@ -5,7 +5,7 @@ use crate::{
post::PostResponse, post::PostResponse,
utils::{check_person_block, get_interface_language, is_mod_or_admin, send_email_to_user}, utils::{check_person_block, get_interface_language, is_mod_or_admin, send_email_to_user},
}; };
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
source::{ source::{
@ -39,10 +39,10 @@ pub async fn build_comment_response(
} }
pub async fn build_community_response( pub async fn build_community_response(
context: &Data<LemmyContext>, context: &LemmyContext,
local_user_view: LocalUserView, local_user_view: LocalUserView,
community_id: CommunityId, community_id: CommunityId,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<Json<CommunityResponse>, LemmyError> {
let is_mod_or_admin = let is_mod_or_admin =
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id) is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id)
.await .await
@ -57,10 +57,10 @@ pub async fn build_community_response(
.await?; .await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(CommunityResponse { Ok(Json(CommunityResponse {
community_view, community_view,
discussion_languages, discussion_languages,
}) }))
} }
pub async fn build_post_response( pub async fn build_post_response(
@ -68,7 +68,7 @@ pub async fn build_post_response(
community_id: CommunityId, community_id: CommunityId,
person_id: PersonId, person_id: PersonId,
post_id: PostId, post_id: PostId,
) -> Result<PostResponse, LemmyError> { ) -> Result<Json<PostResponse>, LemmyError> {
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id) let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id)
.await .await
.is_ok(); .is_ok();
@ -79,7 +79,7 @@ pub async fn build_post_response(
Some(is_mod_or_admin), Some(is_mod_or_admin),
) )
.await?; .await?;
Ok(PostResponse { post_view }) Ok(Json(PostResponse { post_view }))
} }
// TODO: this function is a mess and should be split up to handle email seperately // TODO: this function is a mess and should be split up to handle email seperately

View file

@ -1,10 +1,22 @@
use crate::context::LemmyContext; use crate::{
community::BanFromCommunity,
context::LemmyContext,
person::BanPerson,
post::{DeletePost, RemovePost},
};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::DbUrl, newtypes::{CommunityId, DbUrl, PersonId},
source::{comment::Comment, community::Community, person::Person, post::Post}, source::{
comment::Comment,
community::Community,
person::Person,
post::Post,
private_message::PrivateMessage,
},
}; };
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION}; use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION};
use once_cell::sync::{Lazy, OnceCell}; use once_cell::sync::{Lazy, OnceCell};
use tokio::{ use tokio::{
@ -15,6 +27,7 @@ use tokio::{
}, },
task::JoinHandle, task::JoinHandle,
}; };
use url::Url;
type MatchOutgoingActivitiesBoxed = type MatchOutgoingActivitiesBoxed =
Box<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>; Box<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>;
@ -26,12 +39,27 @@ pub static MATCH_OUTGOING_ACTIVITIES: OnceCell<MatchOutgoingActivitiesBoxed> = O
pub enum SendActivityData { pub enum SendActivityData {
CreatePost(Post), CreatePost(Post),
UpdatePost(Post), UpdatePost(Post),
DeletePost(Post, Person, DeletePost),
RemovePost(Post, Person, RemovePost),
LockPost(Post, Person, bool),
FeaturePost(Post, Person, bool),
CreateComment(Comment), CreateComment(Comment),
UpdateComment(Comment),
DeleteComment(Comment, Person, Community), DeleteComment(Comment, Person, Community),
RemoveComment(Comment, Person, Community, Option<String>), RemoveComment(Comment, Person, Community, Option<String>),
UpdateComment(Comment),
LikePostOrComment(DbUrl, Person, Community, i16), LikePostOrComment(DbUrl, Person, Community, i16),
FollowCommunity(Community, Person, bool), FollowCommunity(Community, Person, bool),
UpdateCommunity(Person, Community),
DeleteCommunity(Person, Community, bool),
RemoveCommunity(Person, Community, Option<String>, bool),
AddModToCommunity(Person, CommunityId, PersonId, bool),
BanFromCommunity(Person, CommunityId, Person, BanFromCommunity),
BanFromSite(Person, Person, BanPerson),
CreatePrivateMessage(PrivateMessageView),
UpdatePrivateMessage(PrivateMessageView),
DeletePrivateMessage(Person, PrivateMessage, bool),
DeleteUser(Person),
CreateReport(Url, Person, Community, String),
} }
// TODO: instead of static, move this into LemmyContext. make sure that stopping the process with // TODO: instead of static, move this into LemmyContext. make sure that stopping the process with

View file

@ -1,6 +1,5 @@
use crate::PerformCrud; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use activitypub_federation::http_signatures::generate_actor_keypair; use actix_web::web::Json;
use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, CreateCommunity}, community::{CommunityResponse, CreateCommunity},
@ -42,107 +41,104 @@ use lemmy_utils::{
}, },
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreateCommunity { pub async fn create_community(
type Response = CommunityResponse; data: Json<CreateCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
#[tracing::instrument(skip(context))] if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { return Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?;
let data: &CreateCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
return Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?;
}
// Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
let name = sanitize_html(&data.name);
let title = sanitize_html(&data.title);
let description = sanitize_html_opt(&data.description);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&name, &slur_regex)?;
check_slurs(&title, &slur_regex)?;
check_slurs_opt(&description, &slur_regex)?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
is_valid_body_field(&data.description, false)?;
// Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community,
&data.name,
&context.settings().get_protocol_and_hostname(),
)?;
let community_dupe =
Community::read_from_apub_id(&mut context.pool(), &community_actor_id).await?;
if community_dupe.is_some() {
return Err(LemmyErrorType::CommunityAlreadyExists)?;
}
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm::builder()
.name(name)
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.actor_id(Some(community_actor_id.clone()))
.private_key(Some(keypair.private_key))
.public_key(keypair.public_key)
.followers_url(Some(generate_followers_url(&community_actor_id)?))
.inbox_url(Some(generate_inbox_url(&community_actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&community_actor_id)?))
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.instance_id(site_view.site.instance_id)
.build();
let inserted_community = Community::create(&mut context.pool(), &community_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityAlreadyExists)?;
// The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
};
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
// Follow your own community
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
pending: false,
};
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
// Update the discussion_languages if that's provided
let community_id = inserted_community.id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
build_community_response(context, local_user_view, community_id).await
} }
// Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
let name = sanitize_html(&data.name);
let title = sanitize_html(&data.title);
let description = sanitize_html_opt(&data.description);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&name, &slur_regex)?;
check_slurs(&title, &slur_regex)?;
check_slurs_opt(&description, &slur_regex)?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
is_valid_body_field(&data.description, false)?;
// Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community,
&data.name,
&context.settings().get_protocol_and_hostname(),
)?;
let community_dupe =
Community::read_from_apub_id(&mut context.pool(), &community_actor_id).await?;
if community_dupe.is_some() {
return Err(LemmyErrorType::CommunityAlreadyExists)?;
}
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm::builder()
.name(name)
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.actor_id(Some(community_actor_id.clone()))
.private_key(Some(keypair.private_key))
.public_key(keypair.public_key)
.followers_url(Some(generate_followers_url(&community_actor_id)?))
.inbox_url(Some(generate_inbox_url(&community_actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&community_actor_id)?))
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.instance_id(site_view.site.instance_id)
.build();
let inserted_community = Community::create(&mut context.pool(), &community_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityAlreadyExists)?;
// The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
};
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
// Follow your own community
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
pending: false,
};
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
// Update the discussion_languages if that's provided
let community_id = inserted_community.id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
build_community_response(&context, local_user_view, community_id).await
} }

View file

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, DeleteCommunity}, community::{CommunityResponse, DeleteCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_top_mod, local_user_view_from_jwt}, utils::{is_top_mod, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -13,36 +14,39 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteCommunity { pub async fn delete_community(
type Response = CommunityResponse; data: Json<DeleteCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] // Fetch the community mods
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { let community_id = data.community_id;
let data: &DeleteCommunity = self; let community_mods =
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Fetch the community mods // Make sure deleter is the top mod
let community_id = data.community_id; is_top_mod(&local_user_view, &community_mods)?;
let community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Make sure deleter is the top mod // Do the delete
is_top_mod(&local_user_view, &community_mods)?; let community_id = data.community_id;
let deleted = data.deleted;
let community = Community::update(
&mut context.pool(),
community_id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Do the delete ActivityChannel::submit_activity(
let community_id = data.community_id; SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted),
let deleted = data.deleted; &context,
Community::update( )
&mut context.pool(), .await?;
community_id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
build_community_response(context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
}
} }

View file

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, RemoveCommunity}, community::{CommunityResponse, RemoveCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt}, utils::{is_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -18,42 +19,50 @@ use lemmy_utils::{
utils::time::naive_from_unix, utils::time::naive_from_unix,
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for RemoveCommunity { pub async fn remove_community(
type Response = CommunityResponse; data: Json<RemoveCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] // Verify its an admin (only an admin can remove a community)
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { is_admin(&local_user_view)?;
let data: &RemoveCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Verify its an admin (only an admin can remove a community) // Do the remove
is_admin(&local_user_view)?; let community_id = data.community_id;
let removed = data.removed;
let community = Community::update(
&mut context.pool(),
community_id,
&CommunityUpdateForm::builder()
.removed(Some(removed))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Do the remove // Mod tables
let community_id = data.community_id; let expires = data.expires.map(naive_from_unix);
let removed = data.removed; let form = ModRemoveCommunityForm {
Community::update( mod_person_id: local_user_view.person.id,
&mut context.pool(), community_id: data.community_id,
community_id, removed: Some(removed),
&CommunityUpdateForm::builder() reason: data.reason.clone(),
.removed(Some(removed)) expires,
.build(), };
) ModRemoveCommunity::create(&mut context.pool(), &form).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Mod tables ActivityChannel::submit_activity(
let expires = data.expires.map(naive_from_unix); SendActivityData::RemoveCommunity(
let form = ModRemoveCommunityForm { local_user_view.person.clone(),
mod_person_id: local_user_view.person.id, community,
community_id: data.community_id, data.reason.clone(),
removed: Some(removed), data.removed,
reason: data.reason.clone(), ),
expires, &context,
}; )
ModRemoveCommunity::create(&mut context.pool(), &form).await?; .await?;
build_community_response(context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
}
} }

View file

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, EditCommunity}, community::{CommunityResponse, EditCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt}, utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -22,65 +23,68 @@ use lemmy_utils::{
utils::{slurs::check_slurs_opt, validation::is_valid_body_field}, utils::{slurs::check_slurs_opt, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditCommunity { pub async fn update_community(
type Response = CommunityResponse; data: Json<EditCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let slur_regex = local_site_to_slur_regex(&local_site);
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { check_slurs_opt(&data.title, &slur_regex)?;
let data: &EditCommunity = self; check_slurs_opt(&data.description, &slur_regex)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; is_valid_body_field(&data.description, false)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site); let title = sanitize_html_opt(&data.title);
check_slurs_opt(&data.title, &slur_regex)?; let description = sanitize_html_opt(&data.description);
check_slurs_opt(&data.description, &slur_regex)?;
is_valid_body_field(&data.description, false)?;
let title = sanitize_html_opt(&data.title); let icon = diesel_option_overwrite_to_url(&data.icon)?;
let description = sanitize_html_opt(&data.description); let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(description);
let icon = diesel_option_overwrite_to_url(&data.icon)?; // Verify its a mod (only mods can edit it)
let banner = diesel_option_overwrite_to_url(&data.banner)?; let community_id = data.community_id;
let description = diesel_option_overwrite(description); let mods: Vec<PersonId> =
CommunityModeratorView::for_community(&mut context.pool(), community_id)
// Verify its a mod (only mods can edit it)
let community_id = data.community_id;
let mods: Vec<PersonId> =
CommunityModeratorView::for_community(&mut context.pool(), community_id)
.await
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())?;
if !mods.contains(&local_user_view.person.id) {
return Err(LemmyErrorType::NotAModerator)?;
}
let community_id = data.community_id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
let community_form = CommunityUpdateForm::builder()
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.updated(Some(Some(naive_now())))
.build();
let community_id = data.community_id;
Community::update(&mut context.pool(), community_id, &community_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?;
if !mods.contains(&local_user_view.person.id) {
build_community_response(context, local_user_view, community_id).await return Err(LemmyErrorType::NotAModerator)?;
} }
let community_id = data.community_id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
let community_form = CommunityUpdateForm::builder()
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.updated(Some(Some(naive_now())))
.build();
let community_id = data.community_id;
let community = Community::update(&mut context.pool(), community_id, &community_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context,
)
.await?;
build_community_response(&context, local_user_view, community_id).await
} }

View file

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
@ -13,41 +13,38 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreateCustomEmoji { pub async fn create_custom_emoji(
type Response = CustomEmojiResponse; data: Json<CreateCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] let local_site = LocalSite::read(&mut context.pool()).await?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> { // Make sure user is an admin
let data: &CreateCustomEmoji = self; is_admin(&local_user_view)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?; let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
// Make sure user is an admin let alt_text = sanitize_html(&data.alt_text);
is_admin(&local_user_view)?; let category = sanitize_html(&data.category);
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim()); let emoji_form = CustomEmojiInsertForm::builder()
let alt_text = sanitize_html(&data.alt_text); .local_site_id(local_site.id)
let category = sanitize_html(&data.category); .shortcode(shortcode)
.alt_text(alt_text)
let emoji_form = CustomEmojiInsertForm::builder() .category(category)
.local_site_id(local_site.id) .image_url(data.clone().image_url.into())
.shortcode(shortcode) .build();
.alt_text(alt_text) let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
.category(category) let mut keywords = vec![];
.image_url(data.clone().image_url.into()) for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build(); .build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; keywords.push(keyword_form);
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
} }

View file

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse}, custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
@ -8,24 +8,18 @@ use lemmy_api_common::{
use lemmy_db_schema::source::custom_emoji::CustomEmoji; use lemmy_db_schema::source::custom_emoji::CustomEmoji;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteCustomEmoji { pub async fn delete_custom_emoji(
type Response = DeleteCustomEmojiResponse; data: Json<DeleteCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<DeleteCustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] // Make sure user is an admin
async fn perform( is_admin(&local_user_view)?;
&self, CustomEmoji::delete(&mut context.pool(), data.id).await?;
context: &Data<LemmyContext>, Ok(Json(DeleteCustomEmojiResponse {
) -> Result<DeleteCustomEmojiResponse, LemmyError> { id: data.id,
let data: &DeleteCustomEmoji = self; success: true,
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; }))
// Make sure user is an admin
is_admin(&local_user_view)?;
CustomEmoji::delete(&mut context.pool(), data.id).await?;
Ok(DeleteCustomEmojiResponse {
id: data.id,
success: true,
})
}
} }

View file

@ -1,3 +1,3 @@
mod create; pub mod create;
mod delete; pub mod delete;
mod update; pub mod update;

View file

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
@ -13,40 +13,37 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditCustomEmoji { pub async fn update_custom_emoji(
type Response = CustomEmojiResponse; data: Json<EditCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] let local_site = LocalSite::read(&mut context.pool()).await?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> { // Make sure user is an admin
let data: &EditCustomEmoji = self; is_admin(&local_user_view)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?; let alt_text = sanitize_html(&data.alt_text);
// Make sure user is an admin let category = sanitize_html(&data.category);
is_admin(&local_user_view)?;
let alt_text = sanitize_html(&data.alt_text); let emoji_form = CustomEmojiUpdateForm::builder()
let category = sanitize_html(&data.category); .local_site_id(local_site.id)
.alt_text(alt_text)
let emoji_form = CustomEmojiUpdateForm::builder() .category(category)
.local_site_id(local_site.id) .image_url(data.clone().image_url.into())
.alt_text(alt_text) .build();
.category(category) let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
.image_url(data.clone().image_url.into()) CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build(); .build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; keywords.push(keyword_form);
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
} }

View file

@ -1,7 +1,3 @@
use actix_web::web::Data;
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyError;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod custom_emoji; pub mod custom_emoji;
@ -9,10 +5,3 @@ pub mod post;
pub mod private_message; pub mod private_message;
pub mod site; pub mod site;
pub mod user; pub mod user;
#[async_trait::async_trait(?Send)]
pub trait PerformCrud {
type Response: serde::ser::Serialize + Send + Clone + Sync;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
}

View file

@ -194,7 +194,5 @@ pub async fn create_post(
} }
}; };
Ok(Json( build_post_response(&context, community_id, person_id, post_id).await
build_post_response(&context, community_id, person_id, post_id).await?,
))
} }

View file

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{DeletePost, PostResponse}, post::{DeletePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt}, utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -12,52 +13,50 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeletePost { pub async fn delete_post(
type Response = PostResponse; data: Json<DeletePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &DeletePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; // Dont delete it if its already been deleted.
let orig_post = Post::read(&mut context.pool(), post_id).await?; if orig_post.deleted == data.deleted {
return Err(LemmyErrorType::CouldntUpdatePost)?;
// Dont delete it if its already been deleted.
if orig_post.deleted == data.deleted {
return Err(LemmyErrorType::CouldntUpdatePost)?;
}
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
// Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Update the post
let post_id = data.post_id;
let deleted = data.deleted;
Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
)
.await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
// Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Update the post
let post = Post::update(
&mut context.pool(),
data.post_id,
&PostUpdateForm::builder()
.deleted(Some(data.deleted))
.build(),
)
.await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::DeletePost(post, local_user_view.person, data.0.clone()),
&context,
)
.await?;
build_post_response(&context, orig_post.community_id, person_id, data.post_id).await
} }

View file

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{PostResponse, RemovePost}, post::{PostResponse, RemovePost},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt}, utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,58 +16,56 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for RemovePost { pub async fn remove_post(
type Response = PostResponse; data: Json<RemovePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &RemovePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_ban( // Verify that only the mods can remove
local_user_view.person.id, is_mod_or_admin(
orig_post.community_id, &mut context.pool(),
&mut context.pool(), local_user_view.person.id,
) orig_post.community_id,
.await?; )
.await?;
// Verify that only the mods can remove // Update the post
is_mod_or_admin( let post_id = data.post_id;
&mut context.pool(), let removed = data.removed;
local_user_view.person.id, let post = Post::update(
orig_post.community_id, &mut context.pool(),
) post_id,
.await?; &PostUpdateForm::builder().removed(Some(removed)).build(),
)
.await?;
// Update the post // Mod tables
let post_id = data.post_id; let form = ModRemovePostForm {
let removed = data.removed; mod_person_id: local_user_view.person.id,
Post::update( post_id: data.post_id,
&mut context.pool(), removed: Some(removed),
post_id, reason: data.reason.clone(),
&PostUpdateForm::builder().removed(Some(removed)).build(), };
) ModRemovePost::create(&mut context.pool(), &form).await?;
.await?;
// Mod tables let person_id = local_user_view.person.id;
let form = ModRemovePostForm { ActivityChannel::submit_activity(
mod_person_id: local_user_view.person.id, SendActivityData::RemovePost(post, local_user_view.person, data.0),
post_id: data.post_id, &context,
removed: Some(removed), )
reason: data.reason.clone(), .await?;
};
ModRemovePost::create(&mut context.pool(), &form).await?;
build_post_response( build_post_response(&context, orig_post.community_id, person_id, post_id).await
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
} }

View file

@ -113,13 +113,11 @@ pub async fn update_post(
ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?; ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?;
Ok(Json( build_post_response(
build_post_response( context.deref(),
context.deref(), orig_post.community_id,
orig_post.community_id, local_user_view.person.id,
local_user_view.person.id, post_id,
post_id, )
) .await
.await?,
))
} }

View file

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{CreatePrivateMessage, PrivateMessageResponse}, private_message::{CreatePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_person_block, check_person_block,
generate_local_apub_endpoint, generate_local_apub_endpoint,
@ -27,78 +28,77 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field}, utils::{slurs::remove_slurs, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreatePrivateMessage { pub async fn create_private_message(
type Response = PrivateMessageResponse; data: Json<CreatePrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(self, context))] let content = sanitize_html(&data.content);
async fn perform( let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
&self, is_valid_body_field(&Some(content.clone()), false)?;
context: &Data<LemmyContext>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &CreatePrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let content = sanitize_html(&data.content); check_person_block(
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); local_user_view.person.id,
is_valid_body_field(&Some(content.clone()), false)?; data.recipient_id,
&mut context.pool(),
)
.await?;
check_person_block( let private_message_form = PrivateMessageInsertForm::builder()
local_user_view.person.id, .content(content.clone())
data.recipient_id, .creator_id(local_user_view.person.id)
&mut context.pool(), .recipient_id(data.recipient_id)
) .build();
.await?;
let private_message_form = PrivateMessageInsertForm::builder() let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form)
.content(content.clone())
.creator_id(local_user_view.person.id)
.recipient_id(data.recipient_id)
.build();
let inserted_private_message =
PrivateMessage::create(&mut context.pool(), &private_message_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
let inserted_private_message_id = inserted_private_message.id;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let apub_id = generate_local_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
&protocol_and_hostname,
)?;
PrivateMessage::update(
&mut context.pool(),
inserted_private_message.id,
&PrivateMessageUpdateForm::builder()
.ap_id(Some(apub_id))
.build(),
)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?; let inserted_private_message_id = inserted_private_message.id;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let apub_id = generate_local_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
&protocol_and_hostname,
)?;
PrivateMessage::update(
&mut context.pool(),
inserted_private_message.id,
&PrivateMessageUpdateForm::builder()
.ap_id(Some(apub_id))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
// Send email to the local recipient, if one exists let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
if view.recipient.local {
let recipient_id = data.recipient_id;
let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?;
let lang = get_interface_language(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let sender_name = &local_user_view.person.name;
send_email_to_user(
&local_recipient,
&lang.notification_private_message_subject(sender_name),
&lang.notification_private_message_body(inbox_link, &content, sender_name),
context.settings(),
)
.await;
}
Ok(PrivateMessageResponse { // Send email to the local recipient, if one exists
private_message_view: view, if view.recipient.local {
}) let recipient_id = data.recipient_id;
let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?;
let lang = get_interface_language(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let sender_name = &local_user_view.person.name;
send_email_to_user(
&local_recipient,
&lang.notification_private_message_subject(sender_name),
&lang.notification_private_message_body(inbox_link, &content, sender_name),
context.settings(),
)
.await;
} }
ActivityChannel::submit_activity(
SendActivityData::CreatePrivateMessage(view.clone()),
&context,
)
.await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View file

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{DeletePrivateMessage, PrivateMessageResponse}, private_message::{DeletePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -12,42 +13,41 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::PrivateMessageView; use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeletePrivateMessage { pub async fn delete_private_message(
type Response = PrivateMessageResponse; data: Json<DeletePrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] // Checking permissions
async fn perform( let private_message_id = data.private_message_id;
&self, let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
context: &Data<LemmyContext>, if local_user_view.person.id != orig_private_message.creator_id {
) -> Result<PrivateMessageResponse, LemmyError> { return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
let data: &DeletePrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Checking permissions
let private_message_id = data.private_message_id;
let orig_private_message =
PrivateMessage::read(&mut context.pool(), private_message_id).await?;
if local_user_view.person.id != orig_private_message.creator_id {
return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
}
// Doing the update
let private_message_id = data.private_message_id;
let deleted = data.deleted;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(PrivateMessageResponse {
private_message_view: view,
})
} }
// Doing the update
let private_message_id = data.private_message_id;
let deleted = data.deleted;
let private_message = PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
ActivityChannel::submit_activity(
SendActivityData::DeletePrivateMessage(local_user_view.person, private_message, data.deleted),
&context,
)
.await?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View file

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{EditPrivateMessage, PrivateMessageResponse}, private_message::{EditPrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html}, utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -19,48 +20,47 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field}, utils::{slurs::remove_slurs, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditPrivateMessage { pub async fn update_private_message(
type Response = PrivateMessageResponse; data: Json<EditPrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(self, context))] // Checking permissions
async fn perform( let private_message_id = data.private_message_id;
&self, let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
context: &Data<LemmyContext>, if local_user_view.person.id != orig_private_message.creator_id {
) -> Result<PrivateMessageResponse, LemmyError> { return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
let data: &EditPrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Checking permissions
let private_message_id = data.private_message_id;
let orig_private_message =
PrivateMessage::read(&mut context.pool(), private_message_id).await?;
if local_user_view.person.id != orig_private_message.creator_id {
return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
}
// Doing the update
let content = sanitize_html(&data.content);
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
is_valid_body_field(&Some(content.clone()), false)?;
let private_message_id = data.private_message_id;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.content(Some(content))
.updated(Some(Some(naive_now())))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(PrivateMessageResponse {
private_message_view: view,
})
} }
// Doing the update
let content = sanitize_html(&data.content);
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
is_valid_body_field(&Some(content.clone()), false)?;
let private_message_id = data.private_message_id;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.content(Some(content))
.updated(Some(Some(naive_now())))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
ActivityChannel::submit_activity(
SendActivityData::UpdatePrivateMessage(view.clone()),
&context,
)
.await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View file

@ -1,6 +1,5 @@
use crate::PerformCrud; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use activitypub_federation::http_signatures::generate_actor_keypair; use actix_web::web::Json;
use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{LoginResponse, Register}, person::{LoginResponse, Register},
@ -38,177 +37,173 @@ use lemmy_utils::{
}, },
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for Register { pub async fn register(
type Response = LoginResponse; data: Json<Register>,
context: Data<LemmyContext>,
) -> Result<Json<LoginResponse>, LemmyError> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
#[tracing::instrument(skip(self, context))] if local_site.registration_mode == RegistrationMode::Closed {
async fn perform(&self, context: &Data<LemmyContext>) -> Result<LoginResponse, LemmyError> { return Err(LemmyErrorType::RegistrationClosed)?;
let data: &Register = self; }
let site_view = SiteView::read_local(&mut context.pool()).await?; password_length_check(&data.password)?;
let local_site = site_view.local_site; honeypot_check(&data.honeypot)?;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
if local_site.registration_mode == RegistrationMode::Closed { if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::RegistrationClosed)?; return Err(LemmyErrorType::EmailRequired)?;
} }
password_length_check(&data.password)?; if local_site.site_setup && require_registration_application && data.answer.is_none() {
honeypot_check(&data.honeypot)?; return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
if local_site.require_email_verification && data.email.is_none() { // Make sure passwords match
return Err(LemmyErrorType::EmailRequired)?; if data.password != data.password_verify {
} return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && require_registration_application && data.answer.is_none() { if local_site.site_setup && local_site.captcha_enabled {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?; if let Some(captcha_uuid) = &data.captcha_uuid {
} let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
let check = CaptchaAnswer::check_captcha(
// Make sure passwords match &mut context.pool(),
if data.password != data.password_verify { CheckCaptchaAnswer {
return Err(LemmyErrorType::PasswordsDoNotMatch)?; uuid,
} answer: data.captcha_answer.clone().unwrap_or_default(),
},
if local_site.site_setup && local_site.captcha_enabled { )
if let Some(captcha_uuid) = &data.captcha_uuid { .await?;
let uuid = uuid::Uuid::parse_str(captcha_uuid)?; if !check {
let check = CaptchaAnswer::check_captcha(
&mut context.pool(),
CheckCaptchaAnswer {
uuid,
answer: data.captcha_answer.clone().unwrap_or_default(),
},
)
.await?;
if !check {
return Err(LemmyErrorType::CaptchaIncorrect)?;
}
} else {
return Err(LemmyErrorType::CaptchaIncorrect)?; return Err(LemmyErrorType::CaptchaIncorrect)?;
} }
} else {
return Err(LemmyErrorType::CaptchaIncorrect)?;
} }
}
let slur_regex = local_site_to_slur_regex(&local_site); let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?; check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username); let username = sanitize_html(&data.username);
let actor_keypair = generate_actor_keypair()?; let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?; is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint( let actor_id = generate_local_apub_endpoint(
EndpointType::Person, EndpointType::Person,
&data.username, &data.username,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?; )?;
if let Some(email) = &data.email { if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? { if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?; return Err(LemmyErrorType::EmailAlreadyExists)?;
}
} }
}
// We have to create both a person, and local_user // We have to create both a person, and local_user
// Register the new person // Register the new person
let person_form = PersonInsertForm::builder() let person_form = PersonInsertForm::builder()
.name(username) .name(username)
.actor_id(Some(actor_id.clone())) .actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key)) .private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key) .public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?)) .inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?)) .shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin // If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup)) .admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id) .instance_id(site_view.site.instance_id)
.build(); .build();
// insert the person // insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form) let inserted_person = Person::create(&mut context.pool(), &person_form)
.await .await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// Automatically set their application as accepted, if they created this with open registration. // Automatically set their application as accepted, if they created this with open registration.
// Also fixes a bug which allows users to log in when registrations are changed to closed. // Also fixes a bug which allows users to log in when registrations are changed to closed.
let accepted_application = Some(!require_registration_application); let accepted_application = Some(!require_registration_application);
// Create the local user // Create the local user
let local_user_form = LocalUserInsertForm::builder() let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id) .person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase)) .email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string()) .password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw)) .show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application) .accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type)) .default_listing_type(Some(local_site.default_post_listing_type))
.build(); .build();
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?; let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
if local_site.site_setup && require_registration_application { if local_site.site_setup && require_registration_application {
// Create the registration application // Create the registration application
let form = RegistrationApplicationInsertForm { let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id, local_user_id: inserted_local_user.id,
// We already made sure answer was not null above // We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"), answer: data.answer.clone().expect("must have an answer"),
};
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
// Email the admins
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
.await?;
}
let mut login_response = LoginResponse {
jwt: None,
registration_created: false,
verify_email_sent: false,
}; };
// Log the user in directly if the site is not setup, or email verification and application aren't required RegistrationApplication::create(&mut context.pool(), &form).await?;
if !local_site.site_setup }
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email( // Email the admins
&local_user_view, if local_site.application_email_admins {
&email, send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
&mut context.pool(), .await?;
context.settings(), }
)
.await?;
login_response.verify_email_sent = true;
}
if require_registration_application { let mut login_response = LoginResponse {
login_response.registration_created = true; jwt: None,
} registration_created: false,
verify_email_sent: false,
};
// Log the user in directly if the site is not setup, or email verification and application aren't required
if !local_site.site_setup
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email(
&local_user_view,
&email,
&mut context.pool(),
context.settings(),
)
.await?;
login_response.verify_email_sent = true;
} }
Ok(login_response) if require_registration_application {
login_response.registration_created = true;
}
} }
Ok(Json(login_response))
} }

View file

@ -1,32 +1,36 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use bcrypt::verify; use bcrypt::verify;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{DeleteAccount, DeleteAccountResponse}, person::{DeleteAccount, DeleteAccountResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteAccount { pub async fn delete_account(
type Response = DeleteAccountResponse; data: Json<DeleteAccount>,
context: Data<LemmyContext>,
) -> Result<Json<DeleteAccountResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(data.auth.as_ref(), &context).await?;
#[tracing::instrument(skip(self, context))] // Verify the password
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError> { let valid: bool = verify(
let data = self; &data.password,
let local_user_view = local_user_view_from_jwt(data.auth.as_ref(), context).await?; &local_user_view.local_user.password_encrypted,
)
// Verify the password .unwrap_or(false);
let valid: bool = verify( if !valid {
&data.password, return Err(LemmyErrorType::IncorrectLogin)?;
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
if !valid {
return Err(LemmyErrorType::IncorrectLogin)?;
}
Ok(DeleteAccountResponse {})
} }
ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person),
&context,
)
.await?;
Ok(Json(DeleteAccountResponse {}))
} }

View file

@ -1,2 +1,2 @@
mod create; pub mod create;
mod delete; pub mod delete;

View file

@ -1,10 +1,9 @@
use crate::{ use crate::{
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, objects::{community::ApubCommunity, instance::ApubSite},
protocol::{ protocol::{
activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser}, activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
objects::{group::Group, instance::Instance}, objects::{group::Group, instance::Instance},
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -12,19 +11,18 @@ use activitypub_federation::{
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use lemmy_api_common::{ use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson};
community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, person::Person, site::Site}, source::{community::Community, person::Person, site::Site},
traits::Crud, traits::Crud,
utils::DbPool, utils::DbPool,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::SiteView;
use lemmy_utils::{error::LemmyError, utils::time::naive_from_unix}; use lemmy_utils::{
error::{LemmyError, LemmyResult},
utils::time::naive_from_unix,
};
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
@ -132,87 +130,74 @@ async fn generate_cc(
}) })
} }
#[async_trait::async_trait] pub(crate) async fn send_ban_from_site(
impl SendActivity for BanPerson { mod_: Person,
type Response = BanPersonResponse; banned_user: Person,
data: BanPerson,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
let expires = data.expires.map(naive_from_unix);
async fn send_activity( // if the action affects a local user, federate to other instances
request: &Self, if banned_user.local {
_response: &Self::Response, if data.ban {
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let person = Person::read(&mut context.pool(), request.person_id).await?;
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
let expires = request.expires.map(naive_from_unix);
// if the action affects a local user, federate to other instances
if person.local {
if request.ban {
BlockUser::send(
&site,
&person.into(),
&local_user_view.person.into(),
request.remove_data.unwrap_or(false),
request.reason.clone(),
expires,
context,
)
.await
} else {
UndoBlockUser::send(
&site,
&person.into(),
&local_user_view.person.into(),
request.reason.clone(),
context,
)
.await
}
} else {
Ok(())
}
}
}
#[async_trait::async_trait]
impl SendActivity for BanFromCommunity {
type Response = BanFromCommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id)
.await?
.into();
let banned_person: ApubPerson = Person::read(&mut context.pool(), request.person_id)
.await?
.into();
let expires = request.expires.map(naive_from_unix);
if request.ban {
BlockUser::send( BlockUser::send(
&SiteOrCommunity::Community(community), &site,
&banned_person, &banned_user.into(),
&local_user_view.person.clone().into(), &mod_.into(),
request.remove_data.unwrap_or(false), data.remove_data.unwrap_or(false),
request.reason.clone(), data.reason.clone(),
expires, expires,
context, &context,
) )
.await .await
} else { } else {
UndoBlockUser::send( UndoBlockUser::send(
&SiteOrCommunity::Community(community), &site,
&banned_person, &banned_user.into(),
&local_user_view.person.clone().into(), &mod_.into(),
request.reason.clone(), data.reason.clone(),
context, &context,
) )
.await .await
} }
} else {
Ok(())
}
}
pub(crate) async fn send_ban_from_community(
mod_: Person,
community_id: CommunityId,
banned_person: Person,
data: BanFromCommunity,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
.await?
.into();
let expires = data.expires.map(naive_from_unix);
if data.ban {
BlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person.into(),
&mod_.into(),
data.remove_data.unwrap_or(false),
data.reason.clone(),
expires,
&context,
)
.await
} else {
UndoBlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person.into(),
&mod_.into(),
data.reason.clone(),
&context,
)
.await
} }
} }

View file

@ -13,7 +13,6 @@ use crate::{
activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove}, activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -22,13 +21,12 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext, context::LemmyContext,
post::{FeaturePost, PostResponse}, utils::{generate_featured_url, generate_moderators_url},
utils::{generate_featured_url, generate_moderators_url, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::community::CollectionType, impls::community::CollectionType,
newtypes::{CommunityId, PersonId},
source::{ source::{
community::{Community, CommunityModerator, CommunityModeratorForm}, community::{Community, CommunityModerator, CommunityModeratorForm},
moderator::{ModAddCommunity, ModAddCommunityForm}, moderator::{ModAddCommunity, ModAddCommunityForm},
@ -165,61 +163,41 @@ impl ActivityHandler for CollectionAdd {
} }
} }
#[async_trait::async_trait] pub(crate) async fn send_add_mod_to_community(
impl SendActivity for AddModToCommunity { actor: Person,
type Response = AddModToCommunityResponse; community_id: CommunityId,
updated_mod_id: PersonId,
async fn send_activity( added: bool,
request: &Self, context: Data<LemmyContext>,
_response: &Self::Response, ) -> Result<(), LemmyError> {
context: &Data<LemmyContext>, let actor: ApubPerson = actor.into();
) -> Result<(), LemmyError> { let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; .await?
let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id) .into();
.await? let updated_mod: ApubPerson = Person::read(&mut context.pool(), updated_mod_id)
.into(); .await?
let updated_mod: ApubPerson = Person::read(&mut context.pool(), request.person_id) .into();
.await? if added {
.into(); CollectionAdd::send_add_mod(&community, &updated_mod, &actor, &context).await
if request.added { } else {
CollectionAdd::send_add_mod( CollectionRemove::send_remove_mod(&community, &updated_mod, &actor, &context).await
&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] pub(crate) async fn send_feature_post(
impl SendActivity for FeaturePost { post: Post,
type Response = PostResponse; actor: Person,
featured: bool,
async fn send_activity( context: Data<LemmyContext>,
request: &Self, ) -> Result<(), LemmyError> {
response: &Self::Response, let actor: ApubPerson = actor.into();
context: &Data<LemmyContext>, let post: ApubPost = post.into();
) -> Result<(), LemmyError> { let community = Community::read(&mut context.pool(), post.community_id)
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; .await?
let community = Community::read(&mut context.pool(), response.post_view.community.id) .into();
.await? if featured {
.into(); CollectionAdd::send_add_featured_post(&community, &post, &actor, &context).await
let post = response.post_view.post.clone().into(); } else {
let person = local_user_view.person.into(); CollectionRemove::send_remove_featured_post(&community, &post, &actor, &context).await
if request.featured {
CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
} else {
CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
}
} }
} }

View file

@ -9,25 +9,23 @@ use crate::{
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
objects::community::ApubCommunity,
protocol::{ protocol::{
activities::community::lock_page::{LockPage, LockType, UndoLockPage}, activities::community::lock_page::{LockPage, LockType, UndoLockPage},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId,
kinds::{activity::UndoType, public}, kinds::{activity::UndoType, public},
traits::ActivityHandler, traits::ActivityHandler,
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
context::LemmyContext,
post::{LockPost, PostResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::Community, community::Community,
person::Person,
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
traits::Crud, traits::Crud,
@ -102,59 +100,47 @@ impl ActivityHandler for UndoLockPage {
} }
} }
#[async_trait::async_trait] pub(crate) async fn send_lock_post(
impl SendActivity for LockPost { post: Post,
type Response = PostResponse; actor: Person,
locked: bool,
async fn send_activity( context: Data<LemmyContext>,
request: &Self, ) -> Result<(), LemmyError> {
response: &Self::Response, let community: ApubCommunity = Community::read(&mut context.pool(), post.community_id)
context: &Data<LemmyContext>, .await?
) -> Result<(), LemmyError> { .into();
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; let id = generate_activity_id(
LockType::Lock,
&context.settings().get_protocol_and_hostname(),
)?;
let community_id = community.actor_id.inner().clone();
let lock = LockPage {
actor: actor.actor_id.clone().into(),
to: vec![public()],
object: ObjectId::from(post.ap_id),
cc: vec![community_id.clone()],
kind: LockType::Lock,
id,
audience: Some(community_id.into()),
};
let activity = if locked {
AnnouncableActivities::LockPost(lock)
} else {
let id = generate_activity_id( let id = generate_activity_id(
LockType::Lock, UndoType::Undo,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?; )?;
let community_id = response.post_view.community.actor_id.clone(); let undo = UndoLockPage {
let actor = local_user_view.person.actor_id.clone().into(); actor: lock.actor.clone(),
let lock = LockPage {
actor,
to: vec![public()], to: vec![public()],
object: response.post_view.post.ap_id.clone().into(), cc: lock.cc.clone(),
cc: vec![community_id.clone().into()], kind: UndoType::Undo,
kind: LockType::Lock,
id, id,
audience: Some(community_id.into()), audience: lock.audience.clone(),
object: lock,
}; };
let activity = if request.locked { AnnouncableActivities::UndoLockPost(undo)
AnnouncableActivities::LockPost(lock) };
} else { send_activity_in_community(activity, &actor.into(), &community, vec![], true, &context).await?;
let id = generate_activity_id( Ok(())
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(&mut 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(())
}
} }

View file

@ -4,7 +4,6 @@ use crate::{
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity}, protocol::{activities::community::report::Report, InCommunity},
PostOrComment, PostOrComment,
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -12,15 +11,12 @@ use activitypub_federation::{
kinds::activity::FlagType, kinds::activity::FlagType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{context::LemmyContext, utils::sanitize_html};
comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext,
post::{CreatePostReport, PostReportResponse},
utils::{local_user_view_from_jwt, sanitize_html},
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
comment_report::{CommentReport, CommentReportForm}, comment_report::{CommentReport, CommentReportForm},
community::Community,
person::Person,
post_report::{PostReport, PostReportForm}, post_report::{PostReport, PostReportForm},
}, },
traits::Reportable, traits::Reportable,
@ -28,58 +24,17 @@ use lemmy_db_schema::{
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait]
impl SendActivity for CreatePostReport {
type Response = PostReportResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
Report::send(
ObjectId::from(response.post_report_view.post.ap_id.clone()),
&local_user_view.person.into(),
ObjectId::from(response.post_report_view.community.actor_id.clone()),
request.reason.to_string(),
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for CreateCommentReport {
type Response = CommentReportResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
Report::send(
ObjectId::from(response.comment_report_view.comment.ap_id.clone()),
&local_user_view.person.into(),
ObjectId::from(response.comment_report_view.community.actor_id.clone()),
request.reason.to_string(),
context,
)
.await
}
}
impl Report { impl Report {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn send( pub(crate) async fn send(
object_id: ObjectId<PostOrComment>, object_id: ObjectId<PostOrComment>,
actor: &ApubPerson, actor: Person,
community_id: ObjectId<ApubCommunity>, community: Community,
reason: String, reason: String,
context: &Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community = community_id.dereference_local(context).await?; let actor: ApubPerson = actor.into();
let community: ApubCommunity = community.into();
let kind = FlagType::Flag; let kind = FlagType::Flag;
let id = generate_activity_id( let id = generate_activity_id(
kind.clone(), kind.clone(),
@ -96,7 +51,7 @@ impl Report {
}; };
let inbox = vec![community.shared_inbox_or_inbox()]; let inbox = vec![community.shared_inbox_or_inbox()];
send_lemmy_activity(context, report, actor, inbox, false).await send_lemmy_activity(&context, report, &actor, inbox, false).await
} }
} }

View file

@ -10,61 +10,43 @@ use crate::{
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::update::UpdateCommunity, InCommunity}, protocol::{activities::community::update::UpdateCommunity, InCommunity},
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::UpdateType, public}, kinds::{activity::UpdateType, public},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{CommunityResponse, EditCommunity, HideCommunity}, use lemmy_db_schema::{
context::LemmyContext, source::{community::Community, person::Person},
utils::local_user_view_from_jwt, traits::Crud,
}; };
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub(crate) async fn send_update_community(
impl SendActivity for EditCommunity { community: Community,
type Response = CommunityResponse; actor: Person,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community: ApubCommunity = community.into();
let actor: ApubPerson = actor.into();
let id = generate_activity_id(
UpdateType::Update,
&context.settings().get_protocol_and_hostname(),
)?;
let update = UpdateCommunity {
actor: actor.id().into(),
to: vec![public()],
object: Box::new(community.clone().into_json(&context).await?),
cc: vec![community.id()],
kind: UpdateType::Update,
id: id.clone(),
audience: Some(community.id().into()),
};
async fn send_activity( let activity = AnnouncableActivities::UpdateCommunity(update);
request: &Self, send_activity_in_community(activity, &actor, &community, vec![], true, &context).await
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
UpdateCommunity::send(community.into(), &local_user_view.person.into(), context).await
}
}
impl UpdateCommunity {
#[tracing::instrument(skip_all)]
pub async fn send(
community: ApubCommunity,
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let id = generate_activity_id(
UpdateType::Update,
&context.settings().get_protocol_and_hostname(),
)?;
let update = UpdateCommunity {
actor: actor.id().into(),
to: vec![public()],
object: Box::new(community.clone().into_json(context).await?),
cc: vec![community.id()],
kind: UpdateType::Update,
id: id.clone(),
audience: Some(community.id().into()),
};
let activity = AnnouncableActivities::UpdateCommunity(update);
send_activity_in_community(activity, actor, &community, vec![], true, context).await
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -101,18 +83,3 @@ impl ActivityHandler for UpdateCommunity {
Ok(()) Ok(())
} }
} }
#[async_trait::async_trait]
impl SendActivity for HideCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
UpdateCommunity::send(community.into(), &local_user_view.person.into(), context).await
}
}

View file

@ -6,92 +6,40 @@ use crate::{
create_or_update::chat_message::CreateOrUpdateChatMessage, create_or_update::chat_message::CreateOrUpdateChatMessage,
CreateOrUpdateType, CreateOrUpdateType,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
context::LemmyContext, use lemmy_db_views::structs::PrivateMessageView;
private_message::{CreatePrivateMessage, EditPrivateMessage, PrivateMessageResponse},
};
use lemmy_db_schema::{
newtypes::PersonId,
source::{person::Person, private_message::PrivateMessage},
traits::Crud,
};
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub(crate) async fn send_create_or_update_pm(
impl SendActivity for CreatePrivateMessage { pm_view: PrivateMessageView,
type Response = PrivateMessageResponse; kind: CreateOrUpdateType,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let actor: ApubPerson = pm_view.creator.into();
let recipient: ApubPerson = pm_view.recipient.into();
async fn send_activity( let id = generate_activity_id(
_request: &Self, kind.clone(),
response: &Self::Response, &context.settings().get_protocol_and_hostname(),
context: &Data<LemmyContext>, )?;
) -> Result<(), LemmyError> { let create_or_update = CreateOrUpdateChatMessage {
CreateOrUpdateChatMessage::send( id: id.clone(),
&response.private_message_view.private_message, actor: actor.id().into(),
response.private_message_view.creator.id, to: [recipient.id().into()],
CreateOrUpdateType::Create, object: ApubPrivateMessage(pm_view.private_message.clone())
context, .into_json(&context)
) .await?,
.await kind,
} };
} let inbox = vec![recipient.shared_inbox_or_inbox()];
#[async_trait::async_trait] send_lemmy_activity(&context, create_or_update, &actor, inbox, true).await
impl SendActivity for EditPrivateMessage {
type Response = PrivateMessageResponse;
async fn send_activity(
_request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
CreateOrUpdateChatMessage::send(
&response.private_message_view.private_message,
response.private_message_view.creator.id,
CreateOrUpdateType::Update,
context,
)
.await
}
}
impl CreateOrUpdateChatMessage {
#[tracing::instrument(skip_all)]
async fn send(
private_message: &PrivateMessage,
sender_id: PersonId,
kind: CreateOrUpdateType,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let recipient_id = private_message.recipient_id;
let sender: ApubPerson = Person::read(&mut context.pool(), sender_id).await?.into();
let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id)
.await?
.into();
let id = generate_activity_id(
kind.clone(),
&context.settings().get_protocol_and_hostname(),
)?;
let create_or_update = CreateOrUpdateChatMessage {
id: id.clone(),
actor: sender.id().into(),
to: [recipient.id().into()],
object: ApubPrivateMessage(private_message.clone())
.into_json(context)
.await?,
kind,
};
let inbox = vec![recipient.shared_inbox_or_inbox()];
send_lemmy_activity(context, create_or_update, &sender, inbox, true).await
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]

View file

@ -3,7 +3,6 @@ use crate::{
insert_received_activity, insert_received_activity,
objects::{instance::remote_instance_inboxes, person::ApubPerson}, objects::{instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::deletion::delete_user::DeleteUser, protocol::activities::deletion::delete_user::DeleteUser,
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -11,50 +10,37 @@ use activitypub_federation::{
protocol::verification::verify_urls_match, protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{context::LemmyContext, utils::delete_user_account};
context::LemmyContext, use lemmy_db_schema::source::person::Person;
person::{DeleteAccount, DeleteAccountResponse},
utils::{delete_user_account, local_user_view_from_jwt},
};
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<(), LemmyError> {
impl SendActivity for DeleteAccount { let actor: ApubPerson = person.into();
type Response = DeleteAccountResponse; delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
async fn send_activity( let id = generate_activity_id(
request: &Self, DeleteType::Delete,
_response: &Self::Response, &context.settings().get_protocol_and_hostname(),
context: &Data<LemmyContext>, )?;
) -> Result<(), LemmyError> { let delete = DeleteUser {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; actor: actor.id().into(),
let actor: ApubPerson = local_user_view.person.into(); to: vec![public()],
delete_user_account( object: actor.id().into(),
actor.id, kind: DeleteType::Delete,
&mut context.pool(), id: id.clone(),
context.settings(), cc: vec![],
context.client(), };
)
.await?;
let id = generate_activity_id( let inboxes = remote_instance_inboxes(&mut context.pool()).await?;
DeleteType::Delete, send_lemmy_activity(&context, delete, &actor, inboxes, true).await?;
&context.settings().get_protocol_and_hostname(), Ok(())
)?;
let delete = DeleteUser {
actor: actor.id().into(),
to: vec![public()],
object: actor.id().into(),
kind: DeleteType::Delete,
id: id.clone(),
cc: vec![],
};
let inboxes = remote_instance_inboxes(&mut context.pool()).await?;
send_lemmy_activity(context, delete, &actor, inboxes, true).await?;
Ok(())
}
} }
/// This can be separate from Delete activity because it doesn't need to be handled in shared inbox /// This can be separate from Delete activity because it doesn't need to be handled in shared inbox

View file

@ -19,7 +19,6 @@ use crate::{
activities::deletion::{delete::Delete, undo_delete::UndoDelete}, activities::deletion::{delete::Delete, undo_delete::UndoDelete},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -28,14 +27,9 @@ use activitypub_federation::{
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{CommunityResponse, DeleteCommunity, RemoveCommunity},
context::LemmyContext,
post::{DeletePost, PostResponse, RemovePost},
private_message::{DeletePrivateMessage, PrivateMessageResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId,
source::{ source::{
comment::{Comment, CommentUpdateForm}, comment::{Comment, CommentUpdateForm},
community::{Community, CommunityUpdateForm}, community::{Community, CommunityUpdateForm},
@ -53,122 +47,6 @@ pub mod delete;
pub mod delete_user; pub mod delete_user;
pub mod undo_delete; pub mod undo_delete;
#[async_trait::async_trait]
impl SendActivity for DeletePost {
type Response = PostResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), response.post_view.community.id).await?;
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for RemovePost {
type Response = PostResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), response.post_view.community.id).await?;
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
request.reason.clone().or_else(|| Some(String::new())),
request.removed,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for DeletePrivateMessage {
type Response = PrivateMessageResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
send_apub_delete_private_message(
&local_user_view.person.into(),
response.private_message_view.private_message.clone(),
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for DeleteCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for RemoveCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
request.reason.clone().or_else(|| Some(String::new())),
request.removed,
context,
)
.await
}
}
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this /// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
/// action was done by a normal user. /// action was done by a normal user.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -200,12 +78,44 @@ pub(crate) async fn send_apub_delete_in_community(
.await .await
} }
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
/// action was done by a normal user.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn send_apub_delete_private_message( pub(crate) async fn send_apub_delete_in_community_new(
actor: Person,
community_id: CommunityId,
object: DeletableObjects,
reason: Option<String>,
deleted: bool,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community = Community::read(&mut context.pool(), community_id).await?;
let actor = ApubPerson::from(actor);
let is_mod_action = reason.is_some();
let activity = if deleted {
let delete = Delete::new(&actor, object, public(), Some(&community), reason, &context)?;
AnnouncableActivities::Delete(delete)
} else {
let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, &context)?;
AnnouncableActivities::UndoDelete(undo)
};
send_activity_in_community(
activity,
&actor,
&community.into(),
vec![],
is_mod_action,
&context,
)
.await
}
#[tracing::instrument(skip_all)]
pub(crate) async fn send_apub_delete_private_message(
actor: &ApubPerson, actor: &ApubPerson,
pm: PrivateMessage, pm: PrivateMessage,
deleted: bool, deleted: bool,
context: &Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let recipient_id = pm.recipient_id; let recipient_id = pm.recipient_id;
let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id) let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id)
@ -215,11 +125,11 @@ async fn send_apub_delete_private_message(
let deletable = DeletableObjects::PrivateMessage(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.id(), None, None, context)?; let delete = Delete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(context, delete, actor, inbox, true).await?; send_lemmy_activity(&context, delete, actor, inbox, true).await?;
} else { } else {
let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, context)?; let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(context, undo, actor, inbox, true).await?; send_lemmy_activity(&context, undo, actor, inbox, true).await?;
}; };
Ok(()) Ok(())
} }

View file

@ -8,12 +8,7 @@ use crate::{
fetcher::user_or_community::UserOrCommunity, fetcher::user_or_community::UserOrCommunity,
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{ protocol::activities::following::{accept::AcceptFollow, follow::Follow},
accept::AcceptFollow,
follow::Follow,
undo_follow::UndoFollow,
},
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -21,17 +16,13 @@ use activitypub_federation::{
protocol::verification::verify_urls_match, protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{BlockCommunity, BlockCommunityResponse},
context::LemmyContext,
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{CommunityFollower, CommunityFollowerForm},
person::{PersonFollower, PersonFollowerForm}, person::{PersonFollower, PersonFollowerForm},
}, },
traits::{Crud, Followable}, traits::Followable,
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
@ -128,18 +119,3 @@ impl ActivityHandler for Follow {
AcceptFollow::send(self, context).await AcceptFollow::send(self, context).await
} }
} }
#[async_trait::async_trait]
impl SendActivity for BlockCommunity {
type Response = BlockCommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
UndoFollow::send(&local_user_view.person.into(), &community.into(), context).await
}
}

View file

@ -1,11 +1,25 @@
use self::following::send_follow_community; use self::following::send_follow_community;
use crate::{ use crate::{
activities::{ activities::{
deletion::{send_apub_delete_in_community, DeletableObjects}, block::{send_ban_from_community, send_ban_from_site},
community::{
collection_add::{send_add_mod_to_community, send_feature_post},
lock_page::send_lock_post,
update::send_update_community,
},
create_or_update::private_message::send_create_or_update_pm,
deletion::{
delete_user::delete_user,
send_apub_delete_in_community,
send_apub_delete_in_community_new,
send_apub_delete_private_message,
DeletableObjects,
},
voting::send_like_activity, voting::send_like_activity,
}, },
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::{ protocol::activities::{
community::report::Report,
create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage}, create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage},
CreateOrUpdateType, CreateOrUpdateType,
}, },
@ -229,14 +243,46 @@ pub async fn match_outgoing_activities(
let fed_task = async { let fed_task = async {
use SendActivityData::*; use SendActivityData::*;
match data { match data {
CreatePost(post) | UpdatePost(post) => { CreatePost(post) => {
let creator_id = post.creator_id; let creator_id = post.creator_id;
CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Create, context).await CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Create, context).await
} }
CreateComment(comment) | UpdateComment(comment) => { UpdatePost(post) => {
let creator_id = post.creator_id;
CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Update, context).await
}
DeletePost(post, person, data) => {
send_apub_delete_in_community_new(
person,
post.community_id,
DeletableObjects::Post(post.into()),
None,
data.deleted,
context,
)
.await
}
RemovePost(post, person, data) => {
send_apub_delete_in_community_new(
person,
post.community_id,
DeletableObjects::Post(post.into()),
data.reason.or_else(|| Some(String::new())),
data.removed,
context,
)
.await
}
LockPost(post, actor, locked) => send_lock_post(post, actor, locked, context).await,
FeaturePost(post, actor, featured) => send_feature_post(post, actor, featured, context).await,
CreateComment(comment) => {
let creator_id = comment.creator_id; let creator_id = comment.creator_id;
CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Create, context).await CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Create, context).await
} }
UpdateComment(comment) => {
let creator_id = comment.creator_id;
CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Update, context).await
}
DeleteComment(comment, actor, community) => { DeleteComment(comment, actor, community) => {
let is_deleted = comment.deleted; let is_deleted = comment.deleted;
let deletable = DeletableObjects::Comment(comment.into()); let deletable = DeletableObjects::Comment(comment.into());
@ -251,9 +297,46 @@ pub async fn match_outgoing_activities(
LikePostOrComment(object_id, person, community, score) => { LikePostOrComment(object_id, person, community, score) => {
send_like_activity(object_id, person, community, score, context).await send_like_activity(object_id, person, community, score, context).await
} }
SendActivityData::FollowCommunity(community, person, follow) => { FollowCommunity(community, person, follow) => {
send_follow_community(community, person, follow, &context).await send_follow_community(community, person, follow, &context).await
} }
UpdateCommunity(actor, community) => send_update_community(community, actor, context).await,
DeleteCommunity(actor, community, removed) => {
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(actor, community, deletable, None, removed, &context).await
}
RemoveCommunity(actor, community, reason, removed) => {
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
actor,
community,
deletable,
reason.clone().or_else(|| Some(String::new())),
removed,
&context,
)
.await
}
AddModToCommunity(actor, community_id, updated_mod_id, added) => {
send_add_mod_to_community(actor, community_id, updated_mod_id, added, context).await
}
BanFromCommunity(mod_, community_id, target, data) => {
send_ban_from_community(mod_, community_id, target, data, context).await
}
BanFromSite(mod_, target, data) => send_ban_from_site(mod_, target, data, context).await,
CreatePrivateMessage(pm) => {
send_create_or_update_pm(pm, CreateOrUpdateType::Create, context).await
}
UpdatePrivateMessage(pm) => {
send_create_or_update_pm(pm, CreateOrUpdateType::Update, context).await
}
DeletePrivateMessage(person, pm, deleted) => {
send_apub_delete_private_message(&person.into(), pm, deleted, context).await
}
DeleteUser(person) => delete_user(person, context).await,
CreateReport(url, actor, community, reason) => {
Report::send(ObjectId::from(url), actor, community, reason, context).await
}
} }
}; };
if *SYNCHRONOUS_FEDERATION { if *SYNCHRONOUS_FEDERATION {

View file

@ -1,33 +1,30 @@
use actix_web::{guard, web, Error, HttpResponse, Result}; use actix_web::{guard, web, Error, HttpResponse, Result};
use lemmy_api::{ use lemmy_api::{
comment::{distinguish::distinguish_comment, like::like_comment, save::save_comment}, comment::{distinguish::distinguish_comment, like::like_comment, save::save_comment},
comment_report::{list::list_comment_reports, resolve::resolve_comment_report}, comment_report::{
community::follow::follow_community, create::create_comment_report,
local_user::notifications::mark_reply_read::mark_reply_as_read, list::list_comment_reports,
post::like::like_post, resolve::resolve_comment_report,
},
community::{
add_mod::add_mod_to_community,
ban::ban_from_community,
block::block_community,
follow::follow_community,
hide::hide_community,
},
local_user::{ban_person::ban_from_site, notifications::mark_reply_read::mark_reply_as_read},
post::{feature::feature_post, like::like_post, lock::lock_post},
post_report::create::create_post_report,
Perform, Perform,
}; };
use lemmy_api_common::{ use lemmy_api_common::{
comment::CreateCommentReport, community::TransferCommunity,
community::{
AddModToCommunity,
BanFromCommunity,
BlockCommunity,
CreateCommunity,
DeleteCommunity,
EditCommunity,
HideCommunity,
RemoveCommunity,
TransferCommunity,
},
context::LemmyContext, context::LemmyContext,
custom_emoji::{CreateCustomEmoji, DeleteCustomEmoji, EditCustomEmoji},
person::{ person::{
AddAdmin, AddAdmin,
BanPerson,
BlockPerson, BlockPerson,
ChangePassword, ChangePassword,
DeleteAccount,
GetBannedPersons, GetBannedPersons,
GetCaptcha, GetCaptcha,
GetPersonMentions, GetPersonMentions,
@ -39,27 +36,12 @@ use lemmy_api_common::{
MarkPersonMentionAsRead, MarkPersonMentionAsRead,
PasswordChangeAfterReset, PasswordChangeAfterReset,
PasswordReset, PasswordReset,
Register,
SaveUserSettings, SaveUserSettings,
VerifyEmail, VerifyEmail,
}, },
post::{ post::{GetSiteMetadata, ListPostReports, MarkPostAsRead, ResolvePostReport, SavePost},
CreatePostReport,
DeletePost,
FeaturePost,
GetSiteMetadata,
ListPostReports,
LockPost,
MarkPostAsRead,
RemovePost,
ResolvePostReport,
SavePost,
},
private_message::{ private_message::{
CreatePrivateMessage,
CreatePrivateMessageReport, CreatePrivateMessageReport,
DeletePrivateMessage,
EditPrivateMessage,
ListPrivateMessageReports, ListPrivateMessageReports,
MarkPrivateMessageAsRead, MarkPrivateMessageAsRead,
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
@ -85,11 +67,33 @@ use lemmy_api_crud::{
remove::remove_comment, remove::remove_comment,
update::update_comment, update::update_comment,
}, },
community::list::list_communities, community::{
post::{create::create_post, read::get_post, update::update_post}, create::create_community,
private_message::read::get_private_message, delete::delete_community,
list::list_communities,
remove::remove_community,
update::update_community,
},
custom_emoji::{
create::create_custom_emoji,
delete::delete_custom_emoji,
update::update_custom_emoji,
},
post::{
create::create_post,
delete::delete_post,
read::get_post,
remove::remove_post,
update::update_post,
},
private_message::{
create::create_private_message,
delete::delete_private_message,
read::get_private_message,
update::update_private_message,
},
site::{create::create_site, read::get_site, update::update_site}, site::{create::create_site, read::get_site, update::update_site},
PerformCrud, user::{create::register, delete::delete_account},
}; };
use lemmy_apub::{ use lemmy_apub::{
api::{ api::{
@ -137,29 +141,23 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
web::resource("/community") web::resource("/community")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(route_post_crud::<CreateCommunity>)), .route(web::post().to(create_community)),
) )
.service( .service(
web::scope("/community") web::scope("/community")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_community)) .route("", web::get().to(get_community))
.route("", web::put().to(route_post_crud::<EditCommunity>)) .route("", web::put().to(update_community))
.route("/hide", web::put().to(route_post::<HideCommunity>)) .route("/hide", web::put().to(hide_community))
.route("/list", web::get().to(list_communities)) .route("/list", web::get().to(list_communities))
.route("/follow", web::post().to(follow_community)) .route("/follow", web::post().to(follow_community))
.route("/block", web::post().to(route_post::<BlockCommunity>)) .route("/block", web::post().to(block_community))
.route( .route("/delete", web::post().to(delete_community))
"/delete",
web::post().to(route_post_crud::<DeleteCommunity>),
)
// Mod Actions // Mod Actions
.route( .route("/remove", web::post().to(remove_community))
"/remove",
web::post().to(route_post_crud::<RemoveCommunity>),
)
.route("/transfer", web::post().to(route_post::<TransferCommunity>)) .route("/transfer", web::post().to(route_post::<TransferCommunity>))
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>)) .route("/ban_user", web::post().to(ban_from_community))
.route("/mod", web::post().to(route_post::<AddModToCommunity>)), .route("/mod", web::post().to(add_mod_to_community)),
) )
.service( .service(
web::scope("/federated_instances") web::scope("/federated_instances")
@ -179,18 +177,18 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_post)) .route("", web::get().to(get_post))
.route("", web::put().to(update_post)) .route("", web::put().to(update_post))
.route("/delete", web::post().to(route_post_crud::<DeletePost>)) .route("/delete", web::post().to(delete_post))
.route("/remove", web::post().to(route_post_crud::<RemovePost>)) .route("/remove", web::post().to(remove_post))
.route( .route(
"/mark_as_read", "/mark_as_read",
web::post().to(route_post::<MarkPostAsRead>), web::post().to(route_post::<MarkPostAsRead>),
) )
.route("/lock", web::post().to(route_post::<LockPost>)) .route("/lock", web::post().to(lock_post))
.route("/feature", web::post().to(route_post::<FeaturePost>)) .route("/feature", web::post().to(feature_post))
.route("/list", web::get().to(list_posts)) .route("/list", web::get().to(list_posts))
.route("/like", web::post().to(like_post)) .route("/like", web::post().to(like_post))
.route("/save", web::put().to(route_post::<SavePost>)) .route("/save", web::put().to(route_post::<SavePost>))
.route("/report", web::post().to(route_post::<CreatePostReport>)) .route("/report", web::post().to(create_post_report))
.route( .route(
"/report/resolve", "/report/resolve",
web::put().to(route_post::<ResolvePostReport>), web::put().to(route_post::<ResolvePostReport>),
@ -221,7 +219,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.route("/like", web::post().to(like_comment)) .route("/like", web::post().to(like_comment))
.route("/save", web::put().to(save_comment)) .route("/save", web::put().to(save_comment))
.route("/list", web::get().to(list_comments)) .route("/list", web::get().to(list_comments))
.route("/report", web::post().to(route_post::<CreateCommentReport>)) .route("/report", web::post().to(create_comment_report))
.route("/report/resolve", web::put().to(resolve_comment_report)) .route("/report/resolve", web::put().to(resolve_comment_report))
.route("/report/list", web::get().to(list_comment_reports)), .route("/report/list", web::get().to(list_comment_reports)),
) )
@ -230,12 +228,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
web::scope("/private_message") web::scope("/private_message")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("/list", web::get().to(get_private_message)) .route("/list", web::get().to(get_private_message))
.route("", web::post().to(route_post_crud::<CreatePrivateMessage>)) .route("", web::post().to(create_private_message))
.route("", web::put().to(route_post_crud::<EditPrivateMessage>)) .route("", web::put().to(update_private_message))
.route( .route("/delete", web::post().to(delete_private_message))
"/delete",
web::post().to(route_post_crud::<DeletePrivateMessage>),
)
.route( .route(
"/mark_as_read", "/mark_as_read",
web::post().to(route_post::<MarkPrivateMessageAsRead>), web::post().to(route_post::<MarkPrivateMessageAsRead>),
@ -260,7 +255,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
web::resource("/user/register") web::resource("/user/register")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(route_post_crud::<Register>)), .route(web::post().to(register)),
) )
.service( .service(
// Handle captcha separately // Handle captcha separately
@ -280,15 +275,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
) )
.route("/replies", web::get().to(route_get::<GetReplies>)) .route("/replies", web::get().to(route_get::<GetReplies>))
// Admin action. I don't like that it's in /user // Admin action. I don't like that it's in /user
.route("/ban", web::post().to(route_post::<BanPerson>)) .route("/ban", web::post().to(ban_from_site))
.route("/banned", web::get().to(route_get::<GetBannedPersons>)) .route("/banned", web::get().to(route_get::<GetBannedPersons>))
.route("/block", web::post().to(route_post::<BlockPerson>)) .route("/block", web::post().to(route_post::<BlockPerson>))
// Account actions. I don't like that they're in /user maybe /accounts // Account actions. I don't like that they're in /user maybe /accounts
.route("/login", web::post().to(route_post::<Login>)) .route("/login", web::post().to(route_post::<Login>))
.route( .route("/delete_account", web::post().to(delete_account))
"/delete_account",
web::post().to(route_post_crud::<DeleteAccount>),
)
.route( .route(
"/password_reset", "/password_reset",
web::post().to(route_post::<PasswordReset>), web::post().to(route_post::<PasswordReset>),
@ -343,12 +335,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.service( .service(
web::scope("/custom_emoji") web::scope("/custom_emoji")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::post().to(route_post_crud::<CreateCustomEmoji>)) .route("", web::post().to(create_custom_emoji))
.route("", web::put().to(route_post_crud::<EditCustomEmoji>)) .route("", web::put().to(update_custom_emoji))
.route( .route("/delete", web::post().to(delete_custom_emoji)),
"/delete",
web::post().to(route_post_crud::<DeleteCustomEmoji>),
),
), ),
); );
} }
@ -408,43 +397,3 @@ where
{ {
perform::<Data>(data.0, context, apub_data).await perform::<Data>(data.0, context, apub_data).await
} }
async fn perform_crud<'a, Data>(
data: Data,
context: web::Data<LemmyContext>,
apub_data: activitypub_federation::config::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
Data: PerformCrud
+ SendActivity<Response = <Data as PerformCrud>::Response>
+ Clone
+ Deserialize<'a>
+ Send
+ 'static,
{
let res = data.perform(&context).await?;
let res_clone = res.clone();
let fed_task = async move { SendActivity::send_activity(&data, &res_clone, &apub_data).await };
if *SYNCHRONOUS_FEDERATION {
fed_task.await?;
} else {
spawn_try_task(fed_task);
}
Ok(HttpResponse::Ok().json(&res))
}
async fn route_post_crud<'a, Data>(
data: web::Json<Data>,
context: web::Data<LemmyContext>,
apub_data: activitypub_federation::config::Data<LemmyContext>,
) -> Result<HttpResponse, Error>
where
Data: PerformCrud
+ SendActivity<Response = <Data as PerformCrud>::Response>
+ Clone
+ Deserialize<'a>
+ Send
+ 'static,
{
perform_crud::<Data>(data.0, context, apub_data).await
}