mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-03-13 15:02:44 +00:00
972 lines
31 KiB
Rust
972 lines
31 KiB
Rust
use crate::structs::{
|
|
CommentReportView,
|
|
LocalUserView,
|
|
PostReportView,
|
|
PrivateMessageReportView,
|
|
ReportCombinedPaginationCursor,
|
|
ReportCombinedView,
|
|
ReportCombinedViewInternal,
|
|
};
|
|
use diesel::{
|
|
result::Error,
|
|
BoolExpressionMethods,
|
|
ExpressionMethods,
|
|
JoinOnDsl,
|
|
NullableExpressionMethods,
|
|
PgExpressionMethods,
|
|
QueryDsl,
|
|
SelectableHelper,
|
|
};
|
|
use diesel_async::RunQueryDsl;
|
|
use i_love_jesus::PaginatedQueryBuilder;
|
|
use lemmy_db_schema::{
|
|
aliases::{self, creator_community_actions},
|
|
newtypes::CommunityId,
|
|
schema::{
|
|
comment,
|
|
comment_actions,
|
|
comment_aggregates,
|
|
comment_report,
|
|
community,
|
|
community_actions,
|
|
local_user,
|
|
person,
|
|
person_actions,
|
|
post,
|
|
post_actions,
|
|
post_aggregates,
|
|
post_report,
|
|
private_message,
|
|
private_message_report,
|
|
report_combined,
|
|
},
|
|
source::{
|
|
combined::report::{report_combined_keys as key, ReportCombined},
|
|
community::CommunityFollower,
|
|
},
|
|
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
|
|
InternalToCombinedView,
|
|
};
|
|
use lemmy_utils::error::LemmyResult;
|
|
|
|
impl ReportCombinedViewInternal {
|
|
/// returns the current unresolved report count for the communities you mod
|
|
pub async fn get_report_count(
|
|
pool: &mut DbPool<'_>,
|
|
user: &LocalUserView,
|
|
community_id: Option<CommunityId>,
|
|
) -> Result<i64, Error> {
|
|
use diesel::dsl::count;
|
|
|
|
let conn = &mut get_conn(pool).await?;
|
|
let my_person_id = user.local_user.person_id;
|
|
|
|
let mut query = report_combined::table
|
|
.left_join(post_report::table)
|
|
.left_join(comment_report::table)
|
|
.left_join(private_message_report::table)
|
|
// Need to join to comment and post to get the community
|
|
.left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
|
|
// The post
|
|
.left_join(
|
|
post::table.on(
|
|
post_report::post_id
|
|
.eq(post::id)
|
|
.or(comment::post_id.eq(post::id)),
|
|
),
|
|
)
|
|
.left_join(community::table.on(post::community_id.eq(community::id)))
|
|
.left_join(actions(
|
|
community_actions::table,
|
|
Some(my_person_id),
|
|
post::community_id,
|
|
))
|
|
.filter(
|
|
post_report::resolved
|
|
.or(comment_report::resolved)
|
|
.or(private_message_report::resolved)
|
|
.is_distinct_from(true),
|
|
)
|
|
.into_boxed();
|
|
|
|
if let Some(community_id) = community_id {
|
|
query = query.filter(post::community_id.eq(community_id))
|
|
}
|
|
|
|
// If its not an admin, get only the ones you mod
|
|
if !user.local_user.admin {
|
|
query = query.filter(community_actions::became_moderator.is_not_null());
|
|
}
|
|
|
|
query
|
|
.select(count(report_combined::id))
|
|
.first::<i64>(conn)
|
|
.await
|
|
}
|
|
}
|
|
|
|
impl ReportCombinedPaginationCursor {
|
|
// get cursor for page that starts immediately after the given post
|
|
pub fn after_post(view: &ReportCombinedView) -> ReportCombinedPaginationCursor {
|
|
let (prefix, id) = match view {
|
|
ReportCombinedView::Comment(v) => ('C', v.comment_report.id.0),
|
|
ReportCombinedView::Post(v) => ('P', v.post_report.id.0),
|
|
ReportCombinedView::PrivateMessage(v) => ('M', v.private_message_report.id.0),
|
|
};
|
|
// hex encoding to prevent ossification
|
|
ReportCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
|
}
|
|
|
|
pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
|
|
let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
|
|
let mut query = report_combined::table
|
|
.select(ReportCombined::as_select())
|
|
.into_boxed();
|
|
let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
|
|
let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
|
|
query = match prefix {
|
|
"C" => query.filter(report_combined::comment_report_id.eq(id)),
|
|
"P" => query.filter(report_combined::post_report_id.eq(id)),
|
|
"M" => query.filter(report_combined::private_message_report_id.eq(id)),
|
|
_ => return Err(err_msg()),
|
|
};
|
|
let token = query.first(&mut get_conn(pool).await?).await?;
|
|
|
|
Ok(PaginationCursorData(token))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct PaginationCursorData(ReportCombined);
|
|
|
|
#[derive(Default)]
|
|
pub struct ReportCombinedQuery {
|
|
pub community_id: Option<CommunityId>,
|
|
pub unresolved_only: Option<bool>,
|
|
pub page_after: Option<PaginationCursorData>,
|
|
pub page_back: Option<bool>,
|
|
}
|
|
|
|
impl ReportCombinedQuery {
|
|
pub async fn list(
|
|
self,
|
|
pool: &mut DbPool<'_>,
|
|
user: &LocalUserView,
|
|
) -> LemmyResult<Vec<ReportCombinedView>> {
|
|
let my_person_id = user.local_user.person_id;
|
|
let report_creator = person::id;
|
|
let item_creator = aliases::person1.field(person::id);
|
|
let resolver = aliases::person2.field(person::id).nullable();
|
|
|
|
let conn = &mut get_conn(pool).await?;
|
|
|
|
// Notes: since the post_report_id and comment_report_id are optional columns,
|
|
// many joins must use an OR condition.
|
|
// For example, the report creator must be the person table joined to either:
|
|
// - post_report.creator_id
|
|
// - comment_report.creator_id
|
|
let mut query = report_combined::table
|
|
.left_join(post_report::table)
|
|
.left_join(comment_report::table)
|
|
.left_join(private_message_report::table)
|
|
// The report creator
|
|
.inner_join(
|
|
person::table.on(
|
|
post_report::creator_id
|
|
.eq(report_creator)
|
|
.or(comment_report::creator_id.eq(report_creator))
|
|
.or(private_message_report::creator_id.eq(report_creator)),
|
|
),
|
|
)
|
|
// The comment
|
|
.left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
|
|
// The private message
|
|
.left_join(
|
|
private_message::table
|
|
.on(private_message_report::private_message_id.eq(private_message::id)),
|
|
)
|
|
// The post
|
|
.left_join(
|
|
post::table.on(
|
|
post_report::post_id
|
|
.eq(post::id)
|
|
.or(comment::post_id.eq(post::id)),
|
|
),
|
|
)
|
|
// The item creator (`item_creator` is the id of this person)
|
|
.inner_join(
|
|
aliases::person1.on(
|
|
post::creator_id
|
|
.eq(item_creator)
|
|
.or(comment::creator_id.eq(item_creator))
|
|
.or(private_message::creator_id.eq(item_creator)),
|
|
),
|
|
)
|
|
// The community
|
|
.left_join(community::table.on(post::community_id.eq(community::id)))
|
|
.left_join(actions_alias(
|
|
creator_community_actions,
|
|
item_creator,
|
|
post::community_id,
|
|
))
|
|
.left_join(
|
|
local_user::table.on(
|
|
item_creator
|
|
.eq(local_user::person_id)
|
|
.and(local_user::admin.eq(true)),
|
|
),
|
|
)
|
|
.left_join(actions(
|
|
community_actions::table,
|
|
Some(my_person_id),
|
|
post::community_id,
|
|
))
|
|
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
|
.left_join(actions(
|
|
person_actions::table,
|
|
Some(my_person_id),
|
|
item_creator,
|
|
))
|
|
.left_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
|
|
.left_join(
|
|
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
|
|
)
|
|
// The resolver
|
|
.left_join(
|
|
aliases::person2.on(
|
|
private_message_report::resolver_id
|
|
.eq(resolver)
|
|
.or(post_report::resolver_id.eq(resolver))
|
|
.or(comment_report::resolver_id.eq(resolver)),
|
|
),
|
|
)
|
|
.left_join(actions(
|
|
comment_actions::table,
|
|
Some(my_person_id),
|
|
comment_report::comment_id,
|
|
))
|
|
.select((
|
|
// Post-specific
|
|
post_report::all_columns.nullable(),
|
|
post::all_columns.nullable(),
|
|
post_aggregates::all_columns.nullable(),
|
|
coalesce(
|
|
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
|
post_aggregates::comments,
|
|
)
|
|
.nullable(),
|
|
post_actions::saved.nullable().is_not_null(),
|
|
post_actions::read.nullable().is_not_null(),
|
|
post_actions::hidden.nullable().is_not_null(),
|
|
post_actions::like_score.nullable(),
|
|
// Comment-specific
|
|
comment_report::all_columns.nullable(),
|
|
comment::all_columns.nullable(),
|
|
comment_aggregates::all_columns.nullable(),
|
|
comment_actions::saved.nullable().is_not_null(),
|
|
comment_actions::like_score.nullable(),
|
|
// Private-message-specific
|
|
private_message_report::all_columns.nullable(),
|
|
private_message::all_columns.nullable(),
|
|
// Shared
|
|
person::all_columns,
|
|
aliases::person1.fields(person::all_columns),
|
|
community::all_columns.nullable(),
|
|
CommunityFollower::select_subscribed_type(),
|
|
aliases::person2.fields(person::all_columns.nullable()),
|
|
local_user::admin.nullable().is_not_null(),
|
|
creator_community_actions
|
|
.field(community_actions::received_ban)
|
|
.nullable()
|
|
.is_not_null(),
|
|
creator_community_actions
|
|
.field(community_actions::became_moderator)
|
|
.nullable()
|
|
.is_not_null(),
|
|
person_actions::blocked.nullable().is_not_null(),
|
|
))
|
|
.into_boxed();
|
|
|
|
if let Some(community_id) = self.community_id {
|
|
query = query.filter(community::id.eq(community_id));
|
|
}
|
|
|
|
// If its not an admin, get only the ones you mod
|
|
if !user.local_user.admin {
|
|
query = query.filter(community_actions::became_moderator.is_not_null());
|
|
}
|
|
|
|
let mut query = PaginatedQueryBuilder::new(query);
|
|
|
|
let page_after = self.page_after.map(|c| c.0);
|
|
|
|
if self.page_back.unwrap_or_default() {
|
|
query = query.before(page_after).limit_and_offset_from_end();
|
|
} else {
|
|
query = query.after(page_after);
|
|
}
|
|
|
|
// If viewing all reports, order by newest, but if viewing unresolved only, show the oldest
|
|
// first (FIFO)
|
|
if self.unresolved_only.unwrap_or_default() {
|
|
query = query
|
|
.filter(
|
|
post_report::resolved
|
|
.or(comment_report::resolved)
|
|
.or(private_message_report::resolved)
|
|
.is_distinct_from(true),
|
|
)
|
|
// TODO: when a `then_asc` method is added, use it here, make the id sort direction match,
|
|
// and remove the separate index; unless additional columns are added to this sort
|
|
.then_desc(ReverseTimestampKey(key::published));
|
|
} else {
|
|
query = query.then_desc(key::published);
|
|
}
|
|
|
|
// Tie breaker
|
|
query = query.then_desc(key::id);
|
|
|
|
let res = query.load::<ReportCombinedViewInternal>(conn).await?;
|
|
|
|
// Map the query results to the enum
|
|
let out = res.into_iter().filter_map(|u| u.map_to_enum()).collect();
|
|
|
|
Ok(out)
|
|
}
|
|
}
|
|
|
|
impl InternalToCombinedView for ReportCombinedViewInternal {
|
|
type CombinedView = ReportCombinedView;
|
|
|
|
fn map_to_enum(&self) -> Option<Self::CombinedView> {
|
|
// Use for a short alias
|
|
let v = self.clone();
|
|
|
|
if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = (
|
|
v.post_report,
|
|
v.post.clone(),
|
|
v.community.clone(),
|
|
v.post_unread_comments,
|
|
v.post_counts,
|
|
) {
|
|
Some(ReportCombinedView::Post(PostReportView {
|
|
post_report,
|
|
post,
|
|
community,
|
|
unread_comments,
|
|
counts,
|
|
creator: v.report_creator,
|
|
post_creator: v.item_creator,
|
|
creator_banned_from_community: v.item_creator_banned_from_community,
|
|
creator_is_moderator: v.item_creator_is_moderator,
|
|
creator_is_admin: v.item_creator_is_admin,
|
|
creator_blocked: v.item_creator_blocked,
|
|
subscribed: v.subscribed,
|
|
saved: v.post_saved,
|
|
read: v.post_read,
|
|
hidden: v.post_hidden,
|
|
my_vote: v.my_post_vote,
|
|
resolver: v.resolver,
|
|
}))
|
|
} else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = (
|
|
v.comment_report,
|
|
v.comment,
|
|
v.comment_counts,
|
|
v.post,
|
|
v.community,
|
|
) {
|
|
Some(ReportCombinedView::Comment(CommentReportView {
|
|
comment_report,
|
|
comment,
|
|
counts,
|
|
post,
|
|
community,
|
|
creator: v.report_creator,
|
|
comment_creator: v.item_creator,
|
|
creator_banned_from_community: v.item_creator_banned_from_community,
|
|
creator_is_moderator: v.item_creator_is_moderator,
|
|
creator_is_admin: v.item_creator_is_admin,
|
|
creator_blocked: v.item_creator_blocked,
|
|
subscribed: v.subscribed,
|
|
saved: v.comment_saved,
|
|
my_vote: v.my_comment_vote,
|
|
resolver: v.resolver,
|
|
}))
|
|
} else if let (Some(private_message_report), Some(private_message)) =
|
|
(v.private_message_report, v.private_message)
|
|
{
|
|
Some(ReportCombinedView::PrivateMessage(
|
|
PrivateMessageReportView {
|
|
private_message_report,
|
|
private_message,
|
|
creator: v.report_creator,
|
|
private_message_creator: v.item_creator,
|
|
resolver: v.resolver,
|
|
},
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[expect(clippy::indexing_slicing)]
|
|
mod tests {
|
|
|
|
use crate::{
|
|
report_combined_view::ReportCombinedQuery,
|
|
structs::{
|
|
CommentReportView,
|
|
LocalUserView,
|
|
PostReportView,
|
|
ReportCombinedView,
|
|
ReportCombinedViewInternal,
|
|
},
|
|
};
|
|
use lemmy_db_schema::{
|
|
aggregates::structs::{CommentAggregates, PostAggregates},
|
|
assert_length,
|
|
source::{
|
|
comment::{Comment, CommentInsertForm},
|
|
comment_report::{CommentReport, CommentReportForm},
|
|
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
|
instance::Instance,
|
|
local_user::{LocalUser, LocalUserInsertForm},
|
|
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
|
person::{Person, PersonInsertForm},
|
|
post::{Post, PostInsertForm},
|
|
post_report::{PostReport, PostReportForm},
|
|
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
|
private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
|
|
},
|
|
traits::{Crud, Joinable, Reportable},
|
|
utils::{build_db_pool_for_tests, DbPool},
|
|
};
|
|
use lemmy_utils::error::LemmyResult;
|
|
use pretty_assertions::assert_eq;
|
|
use serial_test::serial;
|
|
|
|
struct Data {
|
|
instance: Instance,
|
|
timmy: Person,
|
|
sara: Person,
|
|
jessica: Person,
|
|
timmy_view: LocalUserView,
|
|
admin_view: LocalUserView,
|
|
community: Community,
|
|
post: Post,
|
|
post_2: Post,
|
|
comment: Comment,
|
|
}
|
|
|
|
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
|
|
|
let timmy_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_rcv");
|
|
let inserted_timmy = Person::create(pool, &timmy_form).await?;
|
|
let timmy_local_user_form = LocalUserInsertForm::test_form(inserted_timmy.id);
|
|
let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
|
let timmy_view = LocalUserView {
|
|
local_user: timmy_local_user,
|
|
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
|
person: inserted_timmy.clone(),
|
|
counts: Default::default(),
|
|
};
|
|
|
|
// Make an admin, to be able to see private message reports.
|
|
let admin_form = PersonInsertForm::test_form(inserted_instance.id, "admin_rcv");
|
|
let inserted_admin = Person::create(pool, &admin_form).await?;
|
|
let admin_local_user_form = LocalUserInsertForm::test_form_admin(inserted_admin.id);
|
|
let admin_local_user = LocalUser::create(pool, &admin_local_user_form, vec![]).await?;
|
|
let admin_view = LocalUserView {
|
|
local_user: admin_local_user,
|
|
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
|
person: inserted_admin.clone(),
|
|
counts: Default::default(),
|
|
};
|
|
|
|
let sara_form = PersonInsertForm::test_form(inserted_instance.id, "sara_rcv");
|
|
let inserted_sara = Person::create(pool, &sara_form).await?;
|
|
|
|
let jessica_form = PersonInsertForm::test_form(inserted_instance.id, "jessica_mrv");
|
|
let inserted_jessica = Person::create(pool, &jessica_form).await?;
|
|
|
|
let community_form = CommunityInsertForm::new(
|
|
inserted_instance.id,
|
|
"test community crv".to_string(),
|
|
"nada".to_owned(),
|
|
"pubkey".to_string(),
|
|
);
|
|
let inserted_community = Community::create(pool, &community_form).await?;
|
|
|
|
// Make timmy a mod
|
|
let timmy_moderator_form = CommunityModeratorForm {
|
|
community_id: inserted_community.id,
|
|
person_id: inserted_timmy.id,
|
|
};
|
|
CommunityModerator::join(pool, &timmy_moderator_form).await?;
|
|
|
|
let post_form = PostInsertForm::new(
|
|
"A test post crv".into(),
|
|
inserted_timmy.id,
|
|
inserted_community.id,
|
|
);
|
|
let inserted_post = Post::create(pool, &post_form).await?;
|
|
|
|
let new_post_2 = PostInsertForm::new(
|
|
"A test post crv 2".into(),
|
|
inserted_timmy.id,
|
|
inserted_community.id,
|
|
);
|
|
let inserted_post_2 = Post::create(pool, &new_post_2).await?;
|
|
|
|
// Timmy creates a comment
|
|
let comment_form = CommentInsertForm::new(
|
|
inserted_timmy.id,
|
|
inserted_post.id,
|
|
"A test comment rv".into(),
|
|
);
|
|
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
|
|
|
|
Ok(Data {
|
|
instance: inserted_instance,
|
|
timmy: inserted_timmy,
|
|
sara: inserted_sara,
|
|
jessica: inserted_jessica,
|
|
admin_view,
|
|
timmy_view,
|
|
community: inserted_community,
|
|
post: inserted_post,
|
|
post_2: inserted_post_2,
|
|
comment: inserted_comment,
|
|
})
|
|
}
|
|
|
|
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
|
Instance::delete(pool, data.instance.id).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_combined() -> LemmyResult<()> {
|
|
let pool = &build_db_pool_for_tests();
|
|
let pool = &mut pool.into();
|
|
let data = init_data(pool).await?;
|
|
|
|
// sara reports the post
|
|
let sara_report_post_form = PostReportForm {
|
|
creator_id: data.sara.id,
|
|
post_id: data.post.id,
|
|
original_post_name: "Orig post".into(),
|
|
original_post_url: None,
|
|
original_post_body: None,
|
|
reason: "from sara".into(),
|
|
};
|
|
let inserted_post_report = PostReport::report(pool, &sara_report_post_form).await?;
|
|
|
|
// Sara reports the comment
|
|
let sara_report_comment_form = CommentReportForm {
|
|
creator_id: data.sara.id,
|
|
comment_id: data.comment.id,
|
|
original_comment_text: "A test comment rv".into(),
|
|
reason: "from sara".into(),
|
|
};
|
|
CommentReport::report(pool, &sara_report_comment_form).await?;
|
|
|
|
// Timmy creates a private message
|
|
let pm_form = PrivateMessageInsertForm::new(
|
|
data.timmy.id,
|
|
data.sara.id,
|
|
"something offensive crv".to_string(),
|
|
);
|
|
let inserted_pm = PrivateMessage::create(pool, &pm_form).await?;
|
|
|
|
// sara reports private message
|
|
let pm_report_form = PrivateMessageReportForm {
|
|
creator_id: data.sara.id,
|
|
original_pm_text: inserted_pm.content.clone(),
|
|
private_message_id: inserted_pm.id,
|
|
reason: "its offensive".to_string(),
|
|
};
|
|
PrivateMessageReport::report(pool, &pm_report_form).await?;
|
|
|
|
// Do a batch read of admins reports
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.admin_view)
|
|
.await?;
|
|
assert_eq!(3, reports.len());
|
|
|
|
// Make sure the report types are correct
|
|
if let ReportCombinedView::Post(v) = &reports[2] {
|
|
assert_eq!(data.post.id, v.post.id);
|
|
assert_eq!(data.sara.id, v.creator.id);
|
|
assert_eq!(data.timmy.id, v.post_creator.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
if let ReportCombinedView::Comment(v) = &reports[1] {
|
|
assert_eq!(data.comment.id, v.comment.id);
|
|
assert_eq!(data.post.id, v.post.id);
|
|
assert_eq!(data.timmy.id, v.comment_creator.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
if let ReportCombinedView::PrivateMessage(v) = &reports[0] {
|
|
assert_eq!(inserted_pm.id, v.private_message.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
let report_count_admin =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?;
|
|
assert_eq!(3, report_count_admin);
|
|
|
|
// Timmy should only see 2 reports, since they're not an admin,
|
|
// but they do mod the community
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
assert_eq!(2, reports.len());
|
|
|
|
// Make sure the report types are correct
|
|
if let ReportCombinedView::Post(v) = &reports[1] {
|
|
assert_eq!(data.post.id, v.post.id);
|
|
assert_eq!(data.sara.id, v.creator.id);
|
|
assert_eq!(data.timmy.id, v.post_creator.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
if let ReportCombinedView::Comment(v) = &reports[0] {
|
|
assert_eq!(data.comment.id, v.comment.id);
|
|
assert_eq!(data.post.id, v.post.id);
|
|
assert_eq!(data.timmy.id, v.comment_creator.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
let report_count_timmy =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(2, report_count_timmy);
|
|
|
|
// Resolve the post report
|
|
PostReport::resolve(pool, inserted_post_report.id, data.timmy.id).await?;
|
|
|
|
// Do a batch read of timmys reports
|
|
// It should only show saras, which is unresolved
|
|
let reports_after_resolve = ReportCombinedQuery {
|
|
unresolved_only: Some(true),
|
|
..Default::default()
|
|
}
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
assert_length!(1, reports_after_resolve);
|
|
|
|
// Make sure the counts are correct
|
|
let report_count_after_resolved =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(1, report_count_after_resolved);
|
|
|
|
cleanup(data, pool).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_private_message_reports() -> LemmyResult<()> {
|
|
let pool = &build_db_pool_for_tests();
|
|
let pool = &mut pool.into();
|
|
let data = init_data(pool).await?;
|
|
|
|
// timmy sends private message to jessica
|
|
let pm_form = PrivateMessageInsertForm::new(
|
|
data.timmy.id,
|
|
data.jessica.id,
|
|
"something offensive".to_string(),
|
|
);
|
|
let pm = PrivateMessage::create(pool, &pm_form).await?;
|
|
|
|
// jessica reports private message
|
|
let pm_report_form = PrivateMessageReportForm {
|
|
creator_id: data.jessica.id,
|
|
original_pm_text: pm.content.clone(),
|
|
private_message_id: pm.id,
|
|
reason: "its offensive".to_string(),
|
|
};
|
|
let pm_report = PrivateMessageReport::report(pool, &pm_report_form).await?;
|
|
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.admin_view)
|
|
.await?;
|
|
assert_length!(1, reports);
|
|
if let ReportCombinedView::PrivateMessage(v) = &reports[0] {
|
|
assert!(!v.private_message_report.resolved);
|
|
assert_eq!(data.timmy.name, v.private_message_creator.name);
|
|
assert_eq!(data.jessica.name, v.creator.name);
|
|
assert_eq!(pm_report.reason, v.private_message_report.reason);
|
|
assert_eq!(pm.content, v.private_message.content);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
// admin resolves the report (after taking appropriate action)
|
|
PrivateMessageReport::resolve(pool, pm_report.id, data.admin_view.person.id).await?;
|
|
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.admin_view)
|
|
.await?;
|
|
assert_length!(1, reports);
|
|
if let ReportCombinedView::PrivateMessage(v) = &reports[0] {
|
|
assert!(v.private_message_report.resolved);
|
|
assert!(v.resolver.is_some());
|
|
assert_eq!(
|
|
Some(&data.admin_view.person.name),
|
|
v.resolver.as_ref().map(|r| &r.name)
|
|
);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
cleanup(data, pool).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_post_reports() -> LemmyResult<()> {
|
|
let pool = &build_db_pool_for_tests();
|
|
let pool = &mut pool.into();
|
|
let data = init_data(pool).await?;
|
|
|
|
// sara reports
|
|
let sara_report_form = PostReportForm {
|
|
creator_id: data.sara.id,
|
|
post_id: data.post.id,
|
|
original_post_name: "Orig post".into(),
|
|
original_post_url: None,
|
|
original_post_body: None,
|
|
reason: "from sara".into(),
|
|
};
|
|
|
|
PostReport::report(pool, &sara_report_form).await?;
|
|
|
|
// jessica reports
|
|
let jessica_report_form = PostReportForm {
|
|
creator_id: data.jessica.id,
|
|
post_id: data.post_2.id,
|
|
original_post_name: "Orig post".into(),
|
|
original_post_url: None,
|
|
original_post_body: None,
|
|
reason: "from jessica".into(),
|
|
};
|
|
|
|
let inserted_jessica_report = PostReport::report(pool, &jessica_report_form).await?;
|
|
|
|
let read_jessica_report_view =
|
|
PostReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
|
|
|
|
// Make sure the triggers are reading the aggregates correctly.
|
|
let agg_1 = PostAggregates::read(pool, data.post.id).await?;
|
|
let agg_2 = PostAggregates::read(pool, data.post_2.id).await?;
|
|
|
|
assert_eq!(
|
|
read_jessica_report_view.post_report,
|
|
inserted_jessica_report
|
|
);
|
|
assert_eq!(read_jessica_report_view.post, data.post_2);
|
|
assert_eq!(read_jessica_report_view.community.id, data.community.id);
|
|
assert_eq!(read_jessica_report_view.creator.id, data.jessica.id);
|
|
assert_eq!(read_jessica_report_view.post_creator.id, data.timmy.id);
|
|
assert_eq!(read_jessica_report_view.my_vote, None);
|
|
assert_eq!(read_jessica_report_view.resolver, None);
|
|
assert_eq!(agg_1.report_count, 1);
|
|
assert_eq!(agg_1.unresolved_report_count, 1);
|
|
assert_eq!(agg_2.report_count, 1);
|
|
assert_eq!(agg_2.unresolved_report_count, 1);
|
|
|
|
// Do a batch read of timmys reports
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
|
|
if let ReportCombinedView::Post(v) = &reports[1] {
|
|
assert_eq!(v.creator.id, data.sara.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
if let ReportCombinedView::Post(v) = &reports[0] {
|
|
assert_eq!(v.creator.id, data.jessica.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
// Make sure the counts are correct
|
|
let report_count =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(2, report_count);
|
|
|
|
// Pretend the post was removed, and resolve all reports for that object.
|
|
// This is called manually in the API for post removals
|
|
PostReport::resolve_all_for_object(pool, inserted_jessica_report.post_id, data.timmy.id)
|
|
.await?;
|
|
|
|
let read_jessica_report_view_after_resolve =
|
|
PostReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
|
|
assert!(read_jessica_report_view_after_resolve.post_report.resolved);
|
|
assert_eq!(
|
|
read_jessica_report_view_after_resolve
|
|
.post_report
|
|
.resolver_id,
|
|
Some(data.timmy.id)
|
|
);
|
|
assert_eq!(
|
|
read_jessica_report_view_after_resolve
|
|
.resolver
|
|
.map(|r| r.id),
|
|
Some(data.timmy.id)
|
|
);
|
|
|
|
// Make sure the unresolved_post report got decremented in the trigger
|
|
let agg_2 = PostAggregates::read(pool, data.post_2.id).await?;
|
|
assert_eq!(agg_2.report_count, 1);
|
|
assert_eq!(agg_2.unresolved_report_count, 0);
|
|
|
|
// Make sure the other unresolved report isn't changed
|
|
let agg_1 = PostAggregates::read(pool, data.post.id).await?;
|
|
assert_eq!(agg_1.report_count, 1);
|
|
assert_eq!(agg_1.unresolved_report_count, 1);
|
|
|
|
// Do a batch read of timmys reports
|
|
// It should only show saras, which is unresolved
|
|
let reports_after_resolve = ReportCombinedQuery {
|
|
unresolved_only: Some(true),
|
|
..Default::default()
|
|
}
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
|
|
if let ReportCombinedView::Post(v) = &reports_after_resolve[0] {
|
|
assert_length!(1, reports_after_resolve);
|
|
assert_eq!(v.creator.id, data.sara.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
// Make sure the counts are correct
|
|
let report_count_after_resolved =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(1, report_count_after_resolved);
|
|
|
|
cleanup(data, pool).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn test_comment_reports() -> LemmyResult<()> {
|
|
let pool = &build_db_pool_for_tests();
|
|
let pool = &mut pool.into();
|
|
let data = init_data(pool).await?;
|
|
|
|
// sara reports
|
|
let sara_report_form = CommentReportForm {
|
|
creator_id: data.sara.id,
|
|
comment_id: data.comment.id,
|
|
original_comment_text: "this was it at time of creation".into(),
|
|
reason: "from sara".into(),
|
|
};
|
|
|
|
CommentReport::report(pool, &sara_report_form).await?;
|
|
|
|
// jessica reports
|
|
let jessica_report_form = CommentReportForm {
|
|
creator_id: data.jessica.id,
|
|
comment_id: data.comment.id,
|
|
original_comment_text: "this was it at time of creation".into(),
|
|
reason: "from jessica".into(),
|
|
};
|
|
|
|
let inserted_jessica_report = CommentReport::report(pool, &jessica_report_form).await?;
|
|
|
|
let agg = CommentAggregates::read(pool, data.comment.id).await?;
|
|
assert_eq!(agg.report_count, 2);
|
|
|
|
let read_jessica_report_view =
|
|
CommentReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
|
|
assert_eq!(read_jessica_report_view.counts.unresolved_report_count, 2);
|
|
|
|
// Do a batch read of timmys reports
|
|
let reports = ReportCombinedQuery::default()
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
|
|
if let ReportCombinedView::Comment(v) = &reports[0] {
|
|
assert_eq!(v.creator.id, data.jessica.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
if let ReportCombinedView::Comment(v) = &reports[1] {
|
|
assert_eq!(v.creator.id, data.sara.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
// Make sure the counts are correct
|
|
let report_count =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(2, report_count);
|
|
|
|
// Resolve the report
|
|
CommentReport::resolve(pool, inserted_jessica_report.id, data.timmy.id).await?;
|
|
let read_jessica_report_view_after_resolve =
|
|
CommentReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
|
|
|
|
assert!(
|
|
read_jessica_report_view_after_resolve
|
|
.comment_report
|
|
.resolved
|
|
);
|
|
assert_eq!(
|
|
read_jessica_report_view_after_resolve
|
|
.comment_report
|
|
.resolver_id,
|
|
Some(data.timmy.id)
|
|
);
|
|
assert_eq!(
|
|
read_jessica_report_view_after_resolve
|
|
.resolver
|
|
.map(|r| r.id),
|
|
Some(data.timmy.id)
|
|
);
|
|
|
|
// Do a batch read of timmys reports
|
|
// It should only show saras, which is unresolved
|
|
let reports_after_resolve = ReportCombinedQuery {
|
|
unresolved_only: Some(true),
|
|
..Default::default()
|
|
}
|
|
.list(pool, &data.timmy_view)
|
|
.await?;
|
|
|
|
if let ReportCombinedView::Comment(v) = &reports_after_resolve[0] {
|
|
assert_length!(1, reports_after_resolve);
|
|
assert_eq!(v.creator.id, data.sara.id);
|
|
} else {
|
|
panic!("wrong type");
|
|
}
|
|
|
|
// Make sure the counts are correct
|
|
let report_count_after_resolved =
|
|
ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
|
|
assert_eq!(1, report_count_after_resolved);
|
|
|
|
cleanup(data, pool).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|