Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add community reports (only the database part) #4996

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
21 changes: 21 additions & 0 deletions crates/db_schema/replaceable_schema/triggers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,25 @@ END;

$$);

CALL r.create_triggers ('community_report', $$
BEGIN
UPDATE
community_aggregates AS a
SET
report_count = a.report_count + diff.report_count, unresolved_report_count = a.unresolved_report_count + diff.unresolved_report_count
FROM (
SELECT
(community_report).community_id, coalesce(sum(count_diff), 0) AS report_count, coalesce(sum(count_diff) FILTER (WHERE NOT (community_report).resolved), 0) AS unresolved_report_count
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (community_report).community_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.community_id = diff.community_id;

RETURN NULL;

END;

$$);

-- These triggers create and update rows in each aggregates table to match its associated table's rows.
-- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints.
CREATE FUNCTION r.comment_aggregates_from_comment ()
Expand Down Expand Up @@ -685,3 +704,5 @@ CALL r.create_report_combined_trigger ('comment_report');

CALL r.create_report_combined_trigger ('private_message_report');

CALL r.create_report_combined_trigger ('community_report');

2 changes: 2 additions & 0 deletions crates/db_schema/src/aggregates/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub struct CommunityAggregates {
#[serde(skip)]
pub hot_rank: f64,
pub subscribers_local: i64,
pub report_count: i16,
pub unresolved_report_count: i16,
}

#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
Expand Down
97 changes: 97 additions & 0 deletions crates/db_schema/src/impls/community_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::{
newtypes::{CommunityId, CommunityReportId, PersonId},
schema::community_report::{
community_id,
dsl::{community_report, resolved, resolver_id, updated},
},
source::community_report::{CommunityReport, CommunityReportForm},
traits::Reportable,
utils::{get_conn, DbPool},
};
use chrono::Utc;
use diesel::{
dsl::{insert_into, update},
result::Error,
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;

#[async_trait]
impl Reportable for CommunityReport {
type Form = CommunityReportForm;
type IdType = CommunityReportId;
type ObjectIdType = CommunityId;
/// creates a community report and returns it
///
/// * `conn` - the postgres connection
/// * `community_report_form` - the filled CommunityReportForm to insert
async fn report(
pool: &mut DbPool<'_>,
community_report_form: &CommunityReportForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(community_report)
.values(community_report_form)
.get_result::<Self>(conn)
.await
}

/// resolve a community report
///
/// * `conn` - the postgres connection
/// * `report_id` - the id of the report to resolve
/// * `by_resolver_id` - the id of the user resolving the report
async fn resolve(
pool: &mut DbPool<'_>,
report_id_: Self::IdType,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
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_)))
.set((
resolved.eq(true),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
))
.execute(conn)
.await
}

/// unresolve a community report
///
/// * `conn` - the postgres connection
/// * `report_id` - the id of the report to unresolve
/// * `by_resolver_id` - the id of the user unresolving the report
async fn unresolve(
pool: &mut DbPool<'_>,
report_id_: Self::IdType,
by_resolver_id: PersonId,
) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
update(community_report.find(report_id_))
.set((
resolved.eq(false),
resolver_id.eq(by_resolver_id),
updated.eq(Utc::now()),
))
.execute(conn)
.await
}
}
1 change: 1 addition & 0 deletions crates/db_schema/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod comment_reply;
pub mod comment_report;
pub mod community;
pub mod community_block;
pub mod community_report;
pub mod custom_emoji;
pub mod email_verification;
pub mod federation_allowlist;
Expand Down
6 changes: 6 additions & 0 deletions crates/db_schema/src/newtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ pub struct PersonMentionId(i32);
/// The comment report id.
pub struct CommentReportId(pub i32);

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The community report id.
pub struct CommunityReportId(pub i32);

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
Expand Down
25 changes: 25 additions & 0 deletions crates/db_schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ diesel::table! {
users_active_half_year -> Int8,
hot_rank -> Float8,
subscribers_local -> Int8,
report_count -> Int2,
unresolved_report_count -> Int2,
}
}

Expand All @@ -263,6 +265,25 @@ diesel::table! {
}
}

diesel::table! {
community_report (id) {
id -> Int4,
creator_id -> Int4,
community_id -> Int4,
original_community_name -> Text,
original_community_title -> Text,
original_community_description -> Nullable<Text>,
original_community_sidebar -> Nullable<Text>,
original_community_icon -> Nullable<Text>,
original_community_banner -> Nullable<Text>,
reason -> Text,
resolved -> Bool,
resolver_id -> Nullable<Int4>,
published -> Timestamptz,
updated -> Nullable<Timestamptz>,
}
}

diesel::table! {
custom_emoji (id) {
id -> Int4,
Expand Down Expand Up @@ -896,6 +917,7 @@ diesel::table! {
post_report_id -> Nullable<Int4>,
comment_report_id -> Nullable<Int4>,
private_message_report_id -> Nullable<Int4>,
community_report_id -> Nullable<Int4>,
}
}

Expand Down Expand Up @@ -1014,6 +1036,7 @@ diesel::joinable!(community_actions -> community (community_id));
diesel::joinable!(community_aggregates -> community (community_id));
diesel::joinable!(community_language -> community (community_id));
diesel::joinable!(community_language -> language (language_id));
diesel::joinable!(community_report -> community (community_id));
diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
diesel::joinable!(email_verification -> local_user (local_user_id));
diesel::joinable!(federation_allowlist -> instance (instance_id));
Expand Down Expand Up @@ -1068,6 +1091,7 @@ diesel::joinable!(private_message_report -> private_message (private_message_id)
diesel::joinable!(registration_application -> local_user (local_user_id));
diesel::joinable!(registration_application -> person (admin_id));
diesel::joinable!(report_combined -> comment_report (comment_report_id));
diesel::joinable!(report_combined -> community_report (community_report_id));
diesel::joinable!(report_combined -> post_report (post_report_id));
diesel::joinable!(report_combined -> private_message_report (private_message_report_id));
diesel::joinable!(site -> instance (instance_id));
Expand All @@ -1093,6 +1117,7 @@ diesel::allow_tables_to_appear_in_same_query!(
community_actions,
community_aggregates,
community_language,
community_report,
custom_emoji,
custom_emoji_keyword,
email_verification,
Expand Down
60 changes: 60 additions & 0 deletions crates/db_schema/src/source/community_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::newtypes::{CommunityId, CommunityReportId, DbUrl, PersonId};
#[cfg(feature = "full")]
use crate::schema::community_report;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;

#[skip_serializing_none]
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::community::Community))
)]
#[cfg_attr(feature = "full", diesel(table_name = community_report))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A comment report.
pub struct CommunityReport {
pub id: CommunityReportId,
pub creator_id: PersonId,
pub community_id: CommunityId,
pub original_community_name: String,
pub original_community_title: String,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_description: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_sidebar: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_icon: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub original_community_banner: Option<String>,
Comment on lines +35 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the icon and banner are totally necessary, but no reason not to have them I spose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, reports store the original content so the creator of the reported thing can't edit it to remove evidence of the offense. It's possible that a community's icon or banner is the reason for reporting, just like the url of a post.

pub reason: String,
pub resolved: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub resolver_id: Option<PersonId>,
pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", ts(optional))]
pub updated: Option<DateTime<Utc>>,
}

#[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_report))]
pub struct CommunityReportForm {
pub creator_id: PersonId,
pub community_id: CommunityId,
pub original_community_name: String,
pub original_community_title: String,
pub original_community_description: Option<String>,
pub original_community_sidebar: Option<String>,
pub original_community_icon: Option<DbUrl>,
pub original_community_banner: Option<DbUrl>,
pub reason: String,
}
1 change: 1 addition & 0 deletions crates/db_schema/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod comment_reply;
pub mod comment_report;
pub mod community;
pub mod community_block;
pub mod community_report;
pub mod custom_emoji;
pub mod custom_emoji_keyword;
pub mod email_verification;
Expand Down
49 changes: 49 additions & 0 deletions crates/db_views/src/community_report_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::structs::CommunityReportView;
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aliases::{self},
newtypes::{CommunityReportId, PersonId},
schema::{community, community_actions, community_aggregates, community_report, person},
source::community::CommunityFollower,
utils::{actions, get_conn, DbPool},
};

impl CommunityReportView {
/// returns the CommunityReportView for the provided report_id
///
/// * `report_id` - the report id to obtain
pub async fn read(
pool: &mut DbPool<'_>,
report_id: CommunityReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;

community_report::table
.find(report_id)
.inner_join(community::table.inner_join(community_aggregates::table))
.left_join(actions(
community_actions::table,
Some(my_person_id),
community_report::community_id,
))
.inner_join(
aliases::person1.on(community_report::creator_id.eq(aliases::person1.field(person::id))),
)
.left_join(
aliases::person2
.on(community_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
)
.select((
community_report::all_columns,
community::all_columns,
aliases::person1.fields(person::all_columns),
community_aggregates::all_columns,
CommunityFollower::select_subscribed_type(),
aliases::person2.fields(person::all_columns.nullable()),
))
.first(conn)
.await
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed, as now we have the ReportCombinedView. If you need assistance with that tho, I can help, but since the combined reports have been merged now, we should make sure it includes the community reports.

EDIT: Oh I see below you did add it. In that case just remove this file, since everything is done through the combined reports endpoint now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

2 changes: 2 additions & 0 deletions crates/db_views/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub mod comment_report_view;
#[cfg(feature = "full")]
pub mod comment_view;
#[cfg(feature = "full")]
pub mod community_report_view;
#[cfg(feature = "full")]
pub mod custom_emoji_view;
#[cfg(feature = "full")]
pub mod local_image_view;
Expand Down
Loading