Federate "resolve report" action (#5367)

* Federete resolve report action (fixes #4744)

* working

* clippy and fixes

* fix test

* verify mod action

* add workaround for test

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
Nutomic 2025-02-03 23:52:22 +00:00 committed by GitHub
parent a248365834
commit 5fcf166a89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 554 additions and 117 deletions

View file

@ -84,13 +84,11 @@ LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_gamma.hjson \
target/lemmy_server >$LOG_DIR/lemmy_gamma.out 2>&1 &
echo "start delta"
# An instance with only an allowlist for beta
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
target/lemmy_server >$LOG_DIR/lemmy_delta.out 2>&1 &
echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
target/lemmy_server >$LOG_DIR/lemmy_epsilon.out 2>&1 &

View file

@ -39,16 +39,20 @@ import {
listReports,
getMyUser,
listInbox,
allowInstance,
} from "./shared";
import { PostView } from "lemmy-js-client/dist/types/PostView";
import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockInstanceParams";
import {
AddModToCommunity,
EditSite,
LemmyHttp,
PersonPostMentionView,
PostReport,
PostReportView,
ReportCombinedView,
ResolveObject,
ResolvePostReport,
} from "lemmy-js-client";
let betaCommunity: CommunityView | undefined;
@ -57,6 +61,11 @@ beforeAll(async () => {
await setupLogins();
betaCommunity = (await resolveBetaCommunity(alpha)).community;
expect(betaCommunity).toBeDefined();
// Hack: Force outgoing federation queue for beta to be created on epsilon,
// otherwise report test fails
let person = await resolvePerson(epsilon, "@lemmy_beta@lemmy-beta:8551");
expect(person.person).toBeDefined();
});
afterAll(unfollows);
@ -679,16 +688,26 @@ test("Report a post", async () => {
// Create post from alpha
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
await followBeta(alpha);
let postRes = await createPost(alpha, alphaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let alphaPost = await createPost(alpha, alphaCommunity.community.id);
expect(alphaPost.post_view.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
if (!alphaPost) {
throw "Missing alpha post";
}
// add remote mod on epsilon
await followBeta(epsilon);
let betaCommunity = (await resolveBetaCommunity(beta)).community!;
let epsilonUser = (
await resolvePerson(beta, "@lemmy_epsilon@lemmy-epsilon:8581")
).person!;
let mod_params: AddModToCommunity = {
community_id: betaCommunity.community.id,
person_id: epsilonUser.person.id,
added: true,
};
let res = await beta.addModToCommunity(mod_params);
expect(res.moderators.length).toBe(2);
// Send report from gamma
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post!;
let gammaReport = (
await reportPost(gamma, gammaPost.post.id, randomString(10))
).post_report_view.post_report;
@ -732,6 +751,45 @@ test("Report a post", async () => {
//expect(alphaReport.original_post_url).toBe(gammaReport.original_post_url);
expect(alphaReport.original_post_body).toBe(gammaReport.original_post_body);
expect(alphaReport.reason).toBe(gammaReport.reason);
// Report was federated to remote mod instance
let epsilonReport = (
(await waitUntil(
() =>
listReports(epsilon).then(p =>
p.reports.find(r => {
return checkPostReportName(r, gammaReport);
}),
),
res => !!res,
))! as PostReportView
).post_report;
expect(epsilonReport).toBeDefined();
expect(epsilonReport.resolved).toBe(false);
expect(epsilonReport.original_post_name).toBe(gammaReport.original_post_name);
// Resolve report as remote mod
let resolve_params: ResolvePostReport = {
report_id: epsilonReport.id,
resolved: true,
};
let resolve = await epsilon.resolvePostReport(resolve_params);
expect(resolve.post_report_view.post_report.resolved).toBeTruthy();
// Report should be marked resolved on community instance
let resolvedReport = (
(await waitUntil(
() =>
listReports(beta).then(p =>
p.reports.find(r => {
return checkPostReportName(r, gammaReport) && r.resolver != null;
}),
),
res => !!res,
))! as PostReportView
).post_report;
expect(resolvedReport).toBeDefined();
expect(resolvedReport.resolved).toBe(true);
});
test("Fetch post via redirect", async () => {
@ -852,7 +910,6 @@ test("Rewrite markdown links", async () => {
"https://example.com/",
`[link](${postRes1.post_view.post.ap_id})`,
);
console.log(postRes2.post_view.post.body);
expect(postRes2.post_view.post).toBeDefined();
// fetch both posts from another instance

View file

@ -199,7 +199,7 @@ export async function setupLogins() {
}
}
async function allowInstance(api: LemmyHttp, instance: string) {
export async function allowInstance(api: LemmyHttp, instance: string) {
const params: AdminAllowInstanceParams = {
instance,
allow: true,

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
reports::comment::{CommentReportResponse, ResolveCommentReport},
send_activity::{ActivityChannel, SendActivityData},
utils::check_community_mod_action,
};
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
@ -41,6 +43,16 @@ pub async fn resolve_comment_report(
let comment_report_view =
CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::SendResolveReport {
object_id: comment_report_view.comment.ap_id.inner().clone(),
actor: local_user_view.person,
report_creator: report.creator,
community: comment_report_view.community.clone(),
},
&context,
)?;
Ok(Json(CommentReportResponse {
comment_report_view,
}))

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
reports::post::{PostReportResponse, ResolvePostReport},
send_activity::{ActivityChannel, SendActivityData},
utils::check_community_mod_action,
};
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
@ -32,6 +34,7 @@ pub async fn resolve_post_report(
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} else {
// TODO: not federated
PostReport::unresolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
@ -39,5 +42,15 @@ pub async fn resolve_post_report(
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::SendResolveReport {
object_id: post_report_view.post.ap_id.inner().clone(),
actor: local_user_view.person,
report_creator: report.creator,
community: post_report_view.community.clone(),
},
&context,
)?;
Ok(Json(PostReportResponse { post_report_view }))
}

View file

@ -99,6 +99,12 @@ pub enum SendActivityData {
community: Community,
reason: String,
},
SendResolveReport {
object_id: Url,
actor: Person,
report_creator: Person,
community: Community,
},
}
// TODO: instead of static, move this into LemmyContext. make sure that stopping the process with

View file

@ -0,0 +1,14 @@
{
"actor": "http://ds9.lemmy.ml/u/lemmy_user",
"to": ["http://enterprise.lemmy.ml/c/main"],
"type": "Resolve",
"id": "http://ds9.lemmy.ml/activities/flag/4323412-5e45-4a95-a15f-e0dc86361ba4",
"object": {
"actor": "http://ds9.lemmy.ml/u/lemmy_alpha",
"to": ["http://enterprise.lemmy.ml/c/main"],
"object": "http://enterprise.lemmy.ml/post/7",
"summary": "report this post",
"type": "Flag",
"id": "http://ds9.lemmy.ml/activities/flag/98b0933f-5e45-4a95-a15f-e0dc86361ba4"
}
}

View file

@ -1,15 +1,22 @@
use crate::{
activities::send_lemmy_activity,
activity_lists::AnnouncableActivities,
objects::{community::ApubCommunity, person::ApubPerson},
fetcher::post_or_comment::PostOrComment,
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
protocol::activities::community::announce::AnnounceActivity,
};
use activitypub_federation::{config::Data, traits::Actor};
use activitypub_federation::{config::Data, fetch::object_id::ObjectId, traits::Actor};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{activity::ActivitySendTargets, person::PersonFollower},
source::{
activity::ActivitySendTargets,
person::{Person, PersonFollower},
site::Site,
},
traits::Crud,
CommunityVisibility,
};
use lemmy_db_views::structs::CommunityModeratorView;
use lemmy_utils::error::LemmyResult;
pub mod announce;
@ -17,6 +24,7 @@ pub mod collection_add;
pub mod collection_remove;
pub mod lock_page;
pub mod report;
pub mod resolve_report;
pub mod update;
/// This function sends all activities which are happening in a community to the right inboxes.
@ -70,3 +78,37 @@ pub(crate) async fn send_activity_in_community(
send_lemmy_activity(context, activity.clone(), actor, inboxes, false).await?;
Ok(())
}
async fn report_inboxes(
object_id: ObjectId<PostOrComment>,
community: &ApubCommunity,
context: &Data<LemmyContext>,
) -> LemmyResult<ActivitySendTargets> {
// send report to the community where object was posted
let mut inboxes = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox());
if community.local {
// send to all moderators
let moderators =
CommunityModeratorView::for_community(&mut context.pool(), community.id).await?;
for m in moderators {
inboxes.add_inbox(m.moderator.inbox_url.into());
}
// also send report to user's home instance if possible
let object_creator_id = match object_id.dereference_local(context).await? {
PostOrComment::Post(p) => p.creator_id,
PostOrComment::Comment(c) => c.creator_id,
};
let object_creator = Person::read(&mut context.pool(), object_creator_id).await?;
let object_creator_site: Option<ApubSite> =
Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id)
.await
.ok()
.map(Into::into);
if let Some(inbox) = object_creator_site.map(|s| s.shared_inbox_or_inbox()) {
inboxes.add_inbox(inbox);
}
}
Ok(inboxes)
}

View file

@ -1,9 +1,14 @@
use super::report_inboxes;
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
activity_lists::AnnouncableActivities,
insert_received_activity,
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{
activities::community::report::{Report, ReportObject},
activities::community::{
announce::AnnounceActivity,
report::{Report, ReportObject},
},
InCommunity,
},
PostOrComment,
@ -20,71 +25,49 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
activity::ActivitySendTargets,
comment_report::{CommentReport, CommentReportForm},
community::Community,
person::Person,
post_report::{PostReport, PostReportForm},
site::Site,
},
traits::{Crud, Reportable},
traits::Reportable,
};
use lemmy_db_views::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyResult};
use url::Url;
impl Report {
#[tracing::instrument(skip_all)]
pub(crate) async fn send(
object_id: ObjectId<PostOrComment>,
actor: Person,
community: Community,
reason: String,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let actor: ApubPerson = actor.into();
let community: ApubCommunity = community.into();
pub(crate) fn new(
object_id: &ObjectId<PostOrComment>,
actor: &ApubPerson,
community: &ApubCommunity,
reason: Option<String>,
context: &Data<LemmyContext>,
) -> LemmyResult<Self> {
let kind = FlagType::Flag;
let id = generate_activity_id(
kind.clone(),
&context.settings().get_protocol_and_hostname(),
)?;
let report = Report {
Ok(Report {
actor: actor.id().into(),
to: [community.id().into()],
object: ReportObject::Lemmy(object_id.clone()),
summary: Some(reason),
summary: reason,
content: None,
kind,
id: id.clone(),
};
})
}
// send report to the community where object was posted
let mut inboxes = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox());
pub(crate) async fn send(
object_id: ObjectId<PostOrComment>,
actor: &ApubPerson,
community: &ApubCommunity,
reason: String,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let report = Self::new(&object_id, actor, community, Some(reason), &context)?;
let inboxes = report_inboxes(object_id, community, &context).await?;
// send to all moderators
let moderators =
CommunityModeratorView::for_community(&mut context.pool(), community.id).await?;
for m in moderators {
inboxes.add_inbox(m.moderator.actor_id.into());
}
// also send report to user's home instance if possible
let object_creator_id = match object_id.dereference_local(&context).await? {
PostOrComment::Post(p) => p.creator_id,
PostOrComment::Comment(c) => c.creator_id,
};
let object_creator = Person::read(&mut context.pool(), object_creator_id).await?;
let object_creator_site: Option<ApubSite> =
Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id)
.await
.ok()
.map(Into::into);
if let Some(inbox) = object_creator_site.map(|s| s.shared_inbox_or_inbox()) {
inboxes.add_inbox(inbox);
}
send_lemmy_activity(&context, report, &actor, inboxes, false).await
send_lemmy_activity(&context, report, actor, inboxes, false).await
}
}
@ -137,6 +120,17 @@ impl ActivityHandler for Report {
CommentReport::report(&mut context.pool(), &report_form).await?;
}
};
let community = self.community(context).await?;
if community.local {
// forward to remote mods
let object_id = self.object.object_id(context).await?;
let announce = AnnouncableActivities::Report(self);
let announce = AnnounceActivity::new(announce.try_into()?, &community, context)?;
let inboxes = report_inboxes(object_id, &community, context).await?;
send_lemmy_activity(context, announce, &community, inboxes.clone(), false).await?;
}
Ok(())
}
}

View file

@ -0,0 +1,110 @@
use super::report_inboxes;
use crate::{
activities::{
generate_activity_id,
send_lemmy_activity,
verify_mod_action,
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{
activities::community::{
announce::AnnounceActivity,
report::Report,
resolve_report::{ResolveReport, ResolveType},
},
InCommunity,
},
PostOrComment,
};
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{comment_report::CommentReport, post_report::PostReport},
traits::Reportable,
};
use lemmy_utils::error::{LemmyError, LemmyResult};
use url::Url;
impl ResolveReport {
pub(crate) async fn send(
object_id: ObjectId<PostOrComment>,
actor: &ApubPerson,
report_creator: &ApubPerson,
community: &ApubCommunity,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let kind = ResolveType::Resolve;
let id = generate_activity_id(
kind.clone(),
&context.settings().get_protocol_and_hostname(),
)?;
let object = Report::new(&object_id, report_creator, community, None, &context)?;
let resolve = ResolveReport {
actor: actor.id().into(),
to: [community.id().into()],
object,
kind,
id: id.clone(),
};
let inboxes = report_inboxes(object_id, community, &context).await?;
send_lemmy_activity(&context, resolve, actor, inboxes, false).await
}
}
#[async_trait::async_trait]
impl ActivityHandler for ResolveReport {
type DataType = LemmyContext;
type Error = LemmyError;
fn id(&self) -> &Url {
&self.id
}
fn actor(&self) -> &Url {
self.actor.inner()
}
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
self.object.verify(context).await?;
let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?;
verify_urls_match(self.to[0].inner(), self.object.to[0].inner())?;
verify_mod_action(&self.actor, &community, context).await?;
Ok(())
}
async fn receive(self, context: &Data<Self::DataType>) -> LemmyResult<()> {
insert_received_activity(&self.id, context).await?;
let reporter = self.object.actor.dereference(context).await?;
let actor = self.actor.dereference(context).await?;
match self.object.object.dereference(context).await? {
PostOrComment::Post(post) => {
PostReport::resolve_apub(&mut context.pool(), post.id, reporter.id, actor.id).await?;
}
PostOrComment::Comment(comment) => {
CommentReport::resolve_apub(&mut context.pool(), comment.id, reporter.id, actor.id).await?;
}
};
let community = self.community(context).await?;
if community.local {
// forward to remote mods
let object_id = self.object.object.object_id(context).await?;
let announce = AnnouncableActivities::ResolveReport(self);
let announce = AnnounceActivity::new(announce.try_into()?, &community, context)?;
let inboxes = report_inboxes(object_id, &community, context).await?;
send_lemmy_activity(context, announce, &community, inboxes.clone(), false).await?;
}
Ok(())
}
}

View file

@ -18,7 +18,7 @@ use crate::{
},
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::{
community::report::Report,
community::{report::Report, resolve_report::ResolveReport},
create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage},
CreateOrUpdateType,
},
@ -378,7 +378,31 @@ pub async fn match_outgoing_activities(
actor,
community,
reason,
} => Report::send(ObjectId::from(object_id), actor, community, reason, context).await,
} => {
Report::send(
ObjectId::from(object_id),
&actor.into(),
&community.into(),
reason,
context,
)
.await
}
SendResolveReport {
object_id,
actor,
report_creator,
community,
} => {
ResolveReport::send(
ObjectId::from(object_id),
&actor.into(),
&report_creator.into(),
&community.into(),
context,
)
.await
}
AcceptFollower(community_id, person_id) => {
send_accept_or_reject_follow(community_id, person_id, true, &context).await
}

View file

@ -9,6 +9,7 @@ use crate::{
collection_remove::CollectionRemove,
lock_page::{LockPage, UndoLockPage},
report::Report,
resolve_report::ResolveReport,
update::UpdateCommunity,
},
create_or_update::{note_wrapper::CreateOrUpdateNoteWrapper, page::CreateOrUpdatePage},
@ -45,6 +46,7 @@ pub enum SharedInboxActivities {
RejectFollow(RejectFollow),
UndoFollow(UndoFollow),
Report(Report),
ResolveReport(ResolveReport),
AnnounceActivity(AnnounceActivity),
/// This is a catch-all and needs to be last
RawAnnouncableActivities(RawAnnouncableActivities),
@ -67,6 +69,8 @@ pub enum AnnouncableActivities {
CollectionRemove(CollectionRemove),
LockPost(LockPage),
UndoLockPost(UndoLockPage),
Report(Report),
ResolveReport(ResolveReport),
// For compatibility with Pleroma/Mastodon (send only)
Page(Page),
}
@ -89,6 +93,8 @@ impl InCommunity for AnnouncableActivities {
CollectionRemove(a) => a.community(context).await,
LockPost(a) => a.community(context).await,
UndoLockPost(a) => a.community(context).await,
Report(a) => a.community(context).await,
ResolveReport(a) => a.community(context).await,
Page(_) => Err(LemmyErrorType::NotFound.into()),
}
}

View file

@ -17,6 +17,7 @@ use lemmy_utils::{
use moka::future::Cache;
use serde_json::Value;
use std::sync::{Arc, LazyLock};
use tracing::debug;
use url::Url;
pub mod activities;
@ -213,6 +214,7 @@ pub(crate) async fn check_apub_id_valid_with_strictness(
/// This ensures that the same activity doesn't get received and processed more than once, which
/// would be a waste of resources.
async fn insert_received_activity(ap_id: &Url, data: &Data<LemmyContext>) -> LemmyResult<()> {
debug!("Received activity {}", ap_id.to_string());
ReceivedActivity::create(&mut data.pool(), &ap_id.clone().into()).await?;
Ok(())
}

View file

@ -3,10 +3,12 @@ pub mod collection_add;
pub mod collection_remove;
pub mod lock_page;
pub mod report;
pub mod resolve_report;
pub mod update;
#[cfg(test)]
mod tests {
use super::resolve_report::ResolveReport;
use crate::protocol::{
activities::community::{
announce::AnnounceActivity,
@ -44,6 +46,10 @@ mod tests {
)?;
test_parse_lemmy_item::<Report>("assets/lemmy/activities/community/report_page.json")?;
test_parse_lemmy_item::<ResolveReport>(
"assets/lemmy/activities/community/resolve_report_page.json",
)?;
Ok(())
}
}

View file

@ -49,14 +49,17 @@ pub(crate) enum ReportObject {
}
impl ReportObject {
pub async fn dereference(self, context: &Data<LemmyContext>) -> LemmyResult<PostOrComment> {
pub(crate) async fn dereference(
&self,
context: &Data<LemmyContext>,
) -> LemmyResult<PostOrComment> {
match self {
ReportObject::Lemmy(l) => l.dereference(context).await,
ReportObject::Mastodon(objects) => {
for o in objects {
// Find the first reported item which can be dereferenced as post or comment (Lemmy can
// only handle one item per report).
let deref = ObjectId::from(o).dereference(context).await;
let deref = ObjectId::from(o.clone()).dereference(context).await;
if deref.is_ok() {
return deref;
}
@ -65,6 +68,27 @@ impl ReportObject {
}
}
}
pub(crate) async fn object_id(
&self,
context: &Data<LemmyContext>,
) -> LemmyResult<ObjectId<PostOrComment>> {
match self {
ReportObject::Lemmy(l) => Ok(l.clone()),
ReportObject::Mastodon(objects) => {
for o in objects {
// Same logic as above, but return the ID and not the object itself.
let deref = ObjectId::<PostOrComment>::from(o.clone())
.dereference(context)
.await;
if deref.is_ok() {
return Ok(o.clone().into());
}
}
Err(LemmyErrorType::NotFound.into())
}
}
}
}
#[async_trait::async_trait]

View file

@ -0,0 +1,39 @@
use super::report::Report;
use crate::{
objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity,
};
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::helpers::deserialize_one,
};
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyResult;
use serde::{Deserialize, Serialize};
use strum::Display;
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display)]
pub enum ResolveType {
Resolve,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResolveReport {
pub(crate) actor: ObjectId<ApubPerson>,
#[serde(deserialize_with = "deserialize_one")]
pub(crate) to: [ObjectId<ApubCommunity>; 1],
pub(crate) object: Report,
#[serde(rename = "type")]
pub(crate) kind: ResolveType,
pub(crate) id: Url,
}
#[async_trait::async_trait]
impl InCommunity for ResolveReport {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
self.object.community(context).await
}
}

View file

@ -1,9 +1,6 @@
use crate::{
newtypes::{CommentId, CommentReportId, PersonId},
schema::comment_report::{
comment_id,
dsl::{comment_report, resolved, resolver_id, updated},
},
schema::comment_report,
source::comment_report::{CommentReport, CommentReportForm},
traits::Reportable,
utils::{get_conn, DbPool},
@ -12,10 +9,12 @@ use chrono::Utc;
use diesel::{
dsl::{insert_into, update},
result::Error,
BoolExpressionMethods,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::LemmyResult;
#[async_trait]
impl Reportable for CommentReport {
@ -31,7 +30,7 @@ impl Reportable for CommentReport {
comment_report_form: &CommentReportForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(comment_report)
insert_into(comment_report::table)
.values(comment_report_form)
.get_result::<Self>(conn)
.await
@ -48,27 +47,52 @@ impl Reportable for CommentReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(comment_report.find(report_id_))
update(comment_report::table.find(report_id_))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
comment_report::resolved.eq(true),
comment_report::resolver_id.eq(by_resolver_id),
comment_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
}
async fn resolve_apub(
pool: &mut DbPool<'_>,
object_id: Self::ObjectIdType,
report_creator_id: PersonId,
resolver_id: PersonId,
) -> LemmyResult<usize> {
let conn = &mut get_conn(pool).await?;
Ok(
update(
comment_report::table.filter(
comment_report::comment_id
.eq(object_id)
.and(comment_report::creator_id.eq(report_creator_id)),
),
)
.set((
comment_report::resolved.eq(true),
comment_report::resolver_id.eq(resolver_id),
comment_report::updated.eq(Utc::now()),
))
.execute(conn)
.await?,
)
}
async fn resolve_all_for_object(
pool: &mut DbPool<'_>,
comment_id_: CommentId,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(comment_report.filter(comment_id.eq(comment_id_)))
update(comment_report::table.filter(comment_report::comment_id.eq(comment_id_)))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
comment_report::resolved.eq(true),
comment_report::resolver_id.eq(by_resolver_id),
comment_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
@ -85,11 +109,11 @@ impl Reportable for CommentReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(comment_report.find(report_id_))
update(comment_report::table.find(report_id_))
.set((
resolved.eq(false),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
comment_report::resolved.eq(false),
comment_report::resolver_id.eq(by_resolver_id),
comment_report::updated.eq(Utc::now()),
))
.execute(conn)
.await

View file

@ -1,9 +1,6 @@
use crate::{
newtypes::{CommunityId, CommunityReportId, PersonId},
schema::community_report::{
community_id,
dsl::{community_report, resolved, resolver_id, updated},
},
schema::community_report,
source::community_report::{CommunityReport, CommunityReportForm},
traits::Reportable,
utils::{get_conn, DbPool},
@ -12,10 +9,12 @@ use chrono::Utc;
use diesel::{
dsl::{insert_into, update},
result::Error,
BoolExpressionMethods,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::LemmyResult;
#[async_trait]
impl Reportable for CommunityReport {
@ -31,7 +30,7 @@ impl Reportable for CommunityReport {
community_report_form: &CommunityReportForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(community_report)
insert_into(community_report::table)
.values(community_report_form)
.get_result::<Self>(conn)
.await
@ -48,27 +47,52 @@ impl Reportable for CommunityReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
update(community_report::table.find(report_id_))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
community_report::resolved.eq(true),
community_report::resolver_id.eq(by_resolver_id),
community_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
}
async fn resolve_apub(
pool: &mut DbPool<'_>,
object_id: Self::ObjectIdType,
report_creator_id: PersonId,
resolver_id: PersonId,
) -> LemmyResult<usize> {
let conn = &mut get_conn(pool).await?;
Ok(
update(
community_report::table.filter(
community_report::community_id
.eq(object_id)
.and(community_report::creator_id.eq(report_creator_id)),
),
)
.set((
community_report::resolved.eq(true),
community_report::resolver_id.eq(resolver_id),
community_report::updated.eq(Utc::now()),
))
.execute(conn)
.await?,
)
}
async fn resolve_all_for_object(
pool: &mut DbPool<'_>,
community_id_: CommunityId,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.filter(community_id.eq(community_id_)))
update(community_report::table.filter(community_report::community_id.eq(community_id_)))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
community_report::resolved.eq(true),
community_report::resolver_id.eq(by_resolver_id),
community_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
@ -85,11 +109,11 @@ impl Reportable for CommunityReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
update(community_report::table.find(report_id_))
.set((
resolved.eq(false),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
community_report::resolved.eq(false),
community_report::resolver_id.eq(by_resolver_id),
community_report::updated.eq(Utc::now()),
))
.execute(conn)
.await

View file

@ -1,9 +1,7 @@
use crate::{
diesel::BoolExpressionMethods,
newtypes::{PersonId, PostId, PostReportId},
schema::post_report::{
dsl::{post_report, resolved, resolver_id, updated},
post_id,
},
schema::post_report,
source::post_report::{PostReport, PostReportForm},
traits::Reportable,
utils::{get_conn, DbPool},
@ -16,6 +14,7 @@ use diesel::{
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::LemmyResult;
#[async_trait]
impl Reportable for PostReport {
@ -25,7 +24,7 @@ impl Reportable for PostReport {
async fn report(pool: &mut DbPool<'_>, post_report_form: &PostReportForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(post_report)
insert_into(post_report::table)
.values(post_report_form)
.get_result::<Self>(conn)
.await
@ -37,27 +36,52 @@ impl Reportable for PostReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(post_report.find(report_id))
update(post_report::table.find(report_id))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
post_report::resolved.eq(true),
post_report::resolver_id.eq(by_resolver_id),
post_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
}
async fn resolve_apub(
pool: &mut DbPool<'_>,
object_id: Self::ObjectIdType,
report_creator_id: PersonId,
resolver_id: PersonId,
) -> LemmyResult<usize> {
let conn = &mut get_conn(pool).await?;
Ok(
update(
post_report::table.filter(
post_report::post_id
.eq(object_id)
.and(post_report::creator_id.eq(report_creator_id)),
),
)
.set((
post_report::resolved.eq(true),
post_report::resolver_id.eq(resolver_id),
post_report::updated.eq(Utc::now()),
))
.execute(conn)
.await?,
)
}
async fn resolve_all_for_object(
pool: &mut DbPool<'_>,
post_id_: PostId,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(post_report.filter(post_id.eq(post_id_)))
update(post_report::table.filter(post_report::post_id.eq(post_id_)))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
post_report::resolved.eq(true),
post_report::resolver_id.eq(by_resolver_id),
post_report::updated.eq(Utc::now()),
))
.execute(conn)
.await
@ -69,11 +93,11 @@ impl Reportable for PostReport {
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(post_report.find(report_id))
update(post_report::table.find(report_id))
.set((
resolved.eq(false),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
post_report::resolved.eq(false),
post_report::resolver_id.eq(by_resolver_id),
post_report::updated.eq(Utc::now()),
))
.execute(conn)
.await

View file

@ -13,6 +13,7 @@ use diesel::{
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_utils::error::{FederationError, LemmyResult};
#[async_trait]
impl Reportable for PrivateMessageReport {
@ -46,6 +47,14 @@ impl Reportable for PrivateMessageReport {
.execute(conn)
.await
}
async fn resolve_apub(
_pool: &mut DbPool<'_>,
_object_id: Self::ObjectIdType,
_report_creator_id: PersonId,
_resolver_id: PersonId,
) -> LemmyResult<usize> {
Err(FederationError::Unreachable.into())
}
// TODO: this is unused because private message doesn't have remove handler
async fn resolve_all_for_object(

View file

@ -15,6 +15,7 @@ use diesel_async::{
AsyncPgConnection,
RunQueryDsl,
};
use lemmy_utils::error::LemmyResult;
/// Returned by `diesel::delete`
pub type Delete<T> = DeleteStatement<<T as HasTable>::Table, <T as IntoUpdateTarget>::WhereClause>;
@ -154,6 +155,14 @@ pub trait Reportable {
report_id: Self::IdType,
resolver_id: PersonId,
) -> Result<usize, Error>
where
Self: Sized;
async fn resolve_apub(
pool: &mut DbPool<'_>,
object_id: Self::ObjectIdType,
report_creator_id: PersonId,
resolver_id: PersonId,
) -> LemmyResult<usize>
where
Self: Sized;
async fn resolve_all_for_object(