mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-01-15 22:45:56 +00:00
Add community reports (only the database part) (#4996)
* database stuff, not including tests * change migration date * fix community_report_view * update stuff related to report_combined * add db_schema/src/impls/community_report.rs * add report counts to community_aggregates * fix community_report columns and update report_combined_view::tests::test_combined * add column for original sidebar; use None instead of clone; add report_combined_view::tests::test_community_reports * use ts(optional) in CommunityReportView * remove CommunityReportView::read
This commit is contained in:
parent
11e0513592
commit
4d17eef82b
12 changed files with 443 additions and 20 deletions
|
@ -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.
|
-- 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.
|
-- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints.
|
||||||
CREATE FUNCTION r.comment_aggregates_from_comment ()
|
CREATE FUNCTION r.comment_aggregates_from_comment ()
|
||||||
|
@ -685,6 +704,8 @@ CALL r.create_report_combined_trigger ('comment_report');
|
||||||
|
|
||||||
CALL r.create_report_combined_trigger ('private_message_report');
|
CALL r.create_report_combined_trigger ('private_message_report');
|
||||||
|
|
||||||
|
CALL r.create_report_combined_trigger ('community_report');
|
||||||
|
|
||||||
-- person_content (comment, post)
|
-- person_content (comment, post)
|
||||||
CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text)
|
CREATE PROCEDURE r.create_person_content_combined_trigger (table_name text)
|
||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
|
|
|
@ -73,6 +73,8 @@ pub struct CommunityAggregates {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub hot_rank: f64,
|
pub hot_rank: f64,
|
||||||
pub subscribers_local: i64,
|
pub subscribers_local: i64,
|
||||||
|
pub report_count: i16,
|
||||||
|
pub unresolved_report_count: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
|
97
crates/db_schema/src/impls/community_report.rs
Normal file
97
crates/db_schema/src/impls/community_report.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ pub mod comment_reply;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod community_report;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
pub mod email_verification;
|
pub mod email_verification;
|
||||||
pub mod federation_allowlist;
|
pub mod federation_allowlist;
|
||||||
|
|
|
@ -91,6 +91,12 @@ pub struct PersonMentionId(i32);
|
||||||
/// The comment report id.
|
/// The comment report id.
|
||||||
pub struct CommentReportId(pub i32);
|
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)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
|
|
@ -253,6 +253,8 @@ diesel::table! {
|
||||||
users_active_half_year -> Int8,
|
users_active_half_year -> Int8,
|
||||||
hot_rank -> Float8,
|
hot_rank -> Float8,
|
||||||
subscribers_local -> Int8,
|
subscribers_local -> Int8,
|
||||||
|
report_count -> Int2,
|
||||||
|
unresolved_report_count -> Int2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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! {
|
diesel::table! {
|
||||||
custom_emoji (id) {
|
custom_emoji (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -922,6 +943,7 @@ diesel::table! {
|
||||||
post_report_id -> Nullable<Int4>,
|
post_report_id -> Nullable<Int4>,
|
||||||
comment_report_id -> Nullable<Int4>,
|
comment_report_id -> Nullable<Int4>,
|
||||||
private_message_report_id -> Nullable<Int4>,
|
private_message_report_id -> Nullable<Int4>,
|
||||||
|
community_report_id -> Nullable<Int4>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1040,6 +1062,7 @@ diesel::joinable!(community_actions -> community (community_id));
|
||||||
diesel::joinable!(community_aggregates -> community (community_id));
|
diesel::joinable!(community_aggregates -> community (community_id));
|
||||||
diesel::joinable!(community_language -> community (community_id));
|
diesel::joinable!(community_language -> community (community_id));
|
||||||
diesel::joinable!(community_language -> language (language_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!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
|
||||||
diesel::joinable!(email_verification -> local_user (local_user_id));
|
diesel::joinable!(email_verification -> local_user (local_user_id));
|
||||||
diesel::joinable!(federation_allowlist -> instance (instance_id));
|
diesel::joinable!(federation_allowlist -> instance (instance_id));
|
||||||
|
@ -1099,6 +1122,7 @@ diesel::joinable!(private_message_report -> private_message (private_message_id)
|
||||||
diesel::joinable!(registration_application -> local_user (local_user_id));
|
diesel::joinable!(registration_application -> local_user (local_user_id));
|
||||||
diesel::joinable!(registration_application -> person (admin_id));
|
diesel::joinable!(registration_application -> person (admin_id));
|
||||||
diesel::joinable!(report_combined -> comment_report (comment_report_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 -> post_report (post_report_id));
|
||||||
diesel::joinable!(report_combined -> private_message_report (private_message_report_id));
|
diesel::joinable!(report_combined -> private_message_report (private_message_report_id));
|
||||||
diesel::joinable!(site -> instance (instance_id));
|
diesel::joinable!(site -> instance (instance_id));
|
||||||
|
@ -1124,6 +1148,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
community_actions,
|
community_actions,
|
||||||
community_aggregates,
|
community_aggregates,
|
||||||
community_language,
|
community_language,
|
||||||
|
community_report,
|
||||||
custom_emoji,
|
custom_emoji,
|
||||||
custom_emoji_keyword,
|
custom_emoji_keyword,
|
||||||
email_verification,
|
email_verification,
|
||||||
|
|
60
crates/db_schema/src/source/community_report.rs
Normal file
60
crates/db_schema/src/source/community_report.rs
Normal file
|
@ -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>,
|
||||||
|
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,
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ pub mod comment_reply;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod community_report;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
pub mod custom_emoji_keyword;
|
pub mod custom_emoji_keyword;
|
||||||
pub mod email_verification;
|
pub mod email_verification;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
structs::{
|
structs::{
|
||||||
CommentReportView,
|
CommentReportView,
|
||||||
|
CommunityReportView,
|
||||||
LocalUserView,
|
LocalUserView,
|
||||||
PostReportView,
|
PostReportView,
|
||||||
PrivateMessageReportView,
|
PrivateMessageReportView,
|
||||||
|
@ -32,6 +33,8 @@ use lemmy_db_schema::{
|
||||||
comment_report,
|
comment_report,
|
||||||
community,
|
community,
|
||||||
community_actions,
|
community_actions,
|
||||||
|
community_aggregates,
|
||||||
|
community_report,
|
||||||
local_user,
|
local_user,
|
||||||
person,
|
person,
|
||||||
person_actions,
|
person_actions,
|
||||||
|
@ -67,6 +70,7 @@ impl ReportCombinedViewInternal {
|
||||||
.left_join(post_report::table)
|
.left_join(post_report::table)
|
||||||
.left_join(comment_report::table)
|
.left_join(comment_report::table)
|
||||||
.left_join(private_message_report::table)
|
.left_join(private_message_report::table)
|
||||||
|
.left_join(community_report::table)
|
||||||
// Need to join to comment and post to get the community
|
// Need to join to comment and post to get the community
|
||||||
.left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
|
.left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
|
||||||
// The post
|
// The post
|
||||||
|
@ -87,6 +91,7 @@ impl ReportCombinedViewInternal {
|
||||||
post_report::resolved
|
post_report::resolved
|
||||||
.or(comment_report::resolved)
|
.or(comment_report::resolved)
|
||||||
.or(private_message_report::resolved)
|
.or(private_message_report::resolved)
|
||||||
|
.or(community_report::resolved)
|
||||||
.is_distinct_from(true),
|
.is_distinct_from(true),
|
||||||
)
|
)
|
||||||
.into_boxed();
|
.into_boxed();
|
||||||
|
@ -114,6 +119,7 @@ impl ReportCombinedPaginationCursor {
|
||||||
ReportCombinedView::Comment(v) => ('C', v.comment_report.id.0),
|
ReportCombinedView::Comment(v) => ('C', v.comment_report.id.0),
|
||||||
ReportCombinedView::Post(v) => ('P', v.post_report.id.0),
|
ReportCombinedView::Post(v) => ('P', v.post_report.id.0),
|
||||||
ReportCombinedView::PrivateMessage(v) => ('M', v.private_message_report.id.0),
|
ReportCombinedView::PrivateMessage(v) => ('M', v.private_message_report.id.0),
|
||||||
|
ReportCombinedView::Community(v) => ('Y', v.community_report.id.0),
|
||||||
};
|
};
|
||||||
// hex encoding to prevent ossification
|
// hex encoding to prevent ossification
|
||||||
ReportCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
ReportCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
||||||
|
@ -130,6 +136,7 @@ impl ReportCombinedPaginationCursor {
|
||||||
"C" => query.filter(report_combined::comment_report_id.eq(id)),
|
"C" => query.filter(report_combined::comment_report_id.eq(id)),
|
||||||
"P" => query.filter(report_combined::post_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)),
|
"M" => query.filter(report_combined::private_message_report_id.eq(id)),
|
||||||
|
"Y" => query.filter(report_combined::community_report_id.eq(id)),
|
||||||
_ => return Err(err_msg()),
|
_ => return Err(err_msg()),
|
||||||
};
|
};
|
||||||
let token = query.first(&mut get_conn(pool).await?).await?;
|
let token = query.first(&mut get_conn(pool).await?).await?;
|
||||||
|
@ -171,13 +178,15 @@ impl ReportCombinedQuery {
|
||||||
.left_join(post_report::table)
|
.left_join(post_report::table)
|
||||||
.left_join(comment_report::table)
|
.left_join(comment_report::table)
|
||||||
.left_join(private_message_report::table)
|
.left_join(private_message_report::table)
|
||||||
|
.left_join(community_report::table)
|
||||||
// The report creator
|
// The report creator
|
||||||
.inner_join(
|
.inner_join(
|
||||||
person::table.on(
|
person::table.on(
|
||||||
post_report::creator_id
|
post_report::creator_id
|
||||||
.eq(report_creator)
|
.eq(report_creator)
|
||||||
.or(comment_report::creator_id.eq(report_creator))
|
.or(comment_report::creator_id.eq(report_creator))
|
||||||
.or(private_message_report::creator_id.eq(report_creator)),
|
.or(private_message_report::creator_id.eq(report_creator))
|
||||||
|
.or(community_report::creator_id.eq(report_creator)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The comment
|
// The comment
|
||||||
|
@ -196,7 +205,7 @@ impl ReportCombinedQuery {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The item creator (`item_creator` is the id of this person)
|
// The item creator (`item_creator` is the id of this person)
|
||||||
.inner_join(
|
.left_join(
|
||||||
aliases::person1.on(
|
aliases::person1.on(
|
||||||
post::creator_id
|
post::creator_id
|
||||||
.eq(item_creator)
|
.eq(item_creator)
|
||||||
|
@ -205,7 +214,13 @@ impl ReportCombinedQuery {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The community
|
// The community
|
||||||
.left_join(community::table.on(post::community_id.eq(community::id)))
|
.left_join(
|
||||||
|
community::table.on(
|
||||||
|
post::community_id
|
||||||
|
.eq(community::id)
|
||||||
|
.or(community_report::community_id.eq(community::id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.left_join(actions_alias(
|
.left_join(actions_alias(
|
||||||
creator_community_actions,
|
creator_community_actions,
|
||||||
item_creator,
|
item_creator,
|
||||||
|
@ -221,7 +236,7 @@ impl ReportCombinedQuery {
|
||||||
.left_join(actions(
|
.left_join(actions(
|
||||||
community_actions::table,
|
community_actions::table,
|
||||||
Some(my_person_id),
|
Some(my_person_id),
|
||||||
post::community_id,
|
community::id,
|
||||||
))
|
))
|
||||||
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
||||||
.left_join(actions(
|
.left_join(actions(
|
||||||
|
@ -233,13 +248,18 @@ impl ReportCombinedQuery {
|
||||||
.left_join(
|
.left_join(
|
||||||
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
|
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
|
||||||
)
|
)
|
||||||
|
.left_join(
|
||||||
|
community_aggregates::table
|
||||||
|
.on(community_report::community_id.eq(community_aggregates::community_id)),
|
||||||
|
)
|
||||||
// The resolver
|
// The resolver
|
||||||
.left_join(
|
.left_join(
|
||||||
aliases::person2.on(
|
aliases::person2.on(
|
||||||
private_message_report::resolver_id
|
private_message_report::resolver_id
|
||||||
.eq(resolver)
|
.eq(resolver)
|
||||||
.or(post_report::resolver_id.eq(resolver))
|
.or(post_report::resolver_id.eq(resolver))
|
||||||
.or(comment_report::resolver_id.eq(resolver)),
|
.or(comment_report::resolver_id.eq(resolver))
|
||||||
|
.or(community_report::resolver_id.eq(resolver)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.left_join(actions(
|
.left_join(actions(
|
||||||
|
@ -270,9 +290,12 @@ impl ReportCombinedQuery {
|
||||||
// Private-message-specific
|
// Private-message-specific
|
||||||
private_message_report::all_columns.nullable(),
|
private_message_report::all_columns.nullable(),
|
||||||
private_message::all_columns.nullable(),
|
private_message::all_columns.nullable(),
|
||||||
|
// Community-specific
|
||||||
|
community_report::all_columns.nullable(),
|
||||||
|
community_aggregates::all_columns.nullable(),
|
||||||
// Shared
|
// Shared
|
||||||
person::all_columns,
|
person::all_columns,
|
||||||
aliases::person1.fields(person::all_columns),
|
aliases::person1.fields(person::all_columns.nullable()),
|
||||||
community::all_columns.nullable(),
|
community::all_columns.nullable(),
|
||||||
CommunityFollower::select_subscribed_type(),
|
CommunityFollower::select_subscribed_type(),
|
||||||
aliases::person2.fields(person::all_columns.nullable()),
|
aliases::person2.fields(person::all_columns.nullable()),
|
||||||
|
@ -290,12 +313,20 @@ impl ReportCombinedQuery {
|
||||||
.into_boxed();
|
.into_boxed();
|
||||||
|
|
||||||
if let Some(community_id) = self.community_id {
|
if let Some(community_id) = self.community_id {
|
||||||
query = query.filter(community::id.eq(community_id));
|
query = query.filter(
|
||||||
|
community::id
|
||||||
|
.eq(community_id)
|
||||||
|
.and(report_combined::community_report_id.is_null()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If its not an admin, get only the ones you mod
|
// If its not an admin, get only the ones you mod
|
||||||
if !user.local_user.admin {
|
if !user.local_user.admin {
|
||||||
query = query.filter(community_actions::became_moderator.is_not_null());
|
query = query.filter(
|
||||||
|
community_actions::became_moderator
|
||||||
|
.is_not_null()
|
||||||
|
.and(report_combined::community_report_id.is_null()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut query = PaginatedQueryBuilder::new(query);
|
let mut query = PaginatedQueryBuilder::new(query);
|
||||||
|
@ -316,6 +347,7 @@ impl ReportCombinedQuery {
|
||||||
post_report::resolved
|
post_report::resolved
|
||||||
.or(comment_report::resolved)
|
.or(comment_report::resolved)
|
||||||
.or(private_message_report::resolved)
|
.or(private_message_report::resolved)
|
||||||
|
.or(community_report::resolved)
|
||||||
.is_distinct_from(true),
|
.is_distinct_from(true),
|
||||||
)
|
)
|
||||||
// TODO: when a `then_asc` method is added, use it here, make the id sort direction match,
|
// TODO: when a `then_asc` method is added, use it here, make the id sort direction match,
|
||||||
|
@ -344,12 +376,20 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||||
// Use for a short alias
|
// Use for a short alias
|
||||||
let v = self.clone();
|
let v = self.clone();
|
||||||
|
|
||||||
if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = (
|
if let (
|
||||||
|
Some(post_report),
|
||||||
|
Some(post),
|
||||||
|
Some(community),
|
||||||
|
Some(unread_comments),
|
||||||
|
Some(counts),
|
||||||
|
Some(post_creator),
|
||||||
|
) = (
|
||||||
v.post_report,
|
v.post_report,
|
||||||
v.post.clone(),
|
v.post.clone(),
|
||||||
v.community.clone(),
|
v.community.clone(),
|
||||||
v.post_unread_comments,
|
v.post_unread_comments,
|
||||||
v.post_counts,
|
v.post_counts,
|
||||||
|
v.item_creator.clone(),
|
||||||
) {
|
) {
|
||||||
Some(ReportCombinedView::Post(PostReportView {
|
Some(ReportCombinedView::Post(PostReportView {
|
||||||
post_report,
|
post_report,
|
||||||
|
@ -358,7 +398,7 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||||
unread_comments,
|
unread_comments,
|
||||||
counts,
|
counts,
|
||||||
creator: v.report_creator,
|
creator: v.report_creator,
|
||||||
post_creator: v.item_creator,
|
post_creator,
|
||||||
creator_banned_from_community: v.item_creator_banned_from_community,
|
creator_banned_from_community: v.item_creator_banned_from_community,
|
||||||
creator_is_moderator: v.item_creator_is_moderator,
|
creator_is_moderator: v.item_creator_is_moderator,
|
||||||
creator_is_admin: v.item_creator_is_admin,
|
creator_is_admin: v.item_creator_is_admin,
|
||||||
|
@ -370,12 +410,20 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||||
my_vote: v.my_post_vote,
|
my_vote: v.my_post_vote,
|
||||||
resolver: v.resolver,
|
resolver: v.resolver,
|
||||||
}))
|
}))
|
||||||
} else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = (
|
} else if let (
|
||||||
|
Some(comment_report),
|
||||||
|
Some(comment),
|
||||||
|
Some(counts),
|
||||||
|
Some(post),
|
||||||
|
Some(community),
|
||||||
|
Some(comment_creator),
|
||||||
|
) = (
|
||||||
v.comment_report,
|
v.comment_report,
|
||||||
v.comment,
|
v.comment,
|
||||||
v.comment_counts,
|
v.comment_counts,
|
||||||
v.post,
|
v.post,
|
||||||
v.community,
|
v.community.clone(),
|
||||||
|
v.item_creator.clone(),
|
||||||
) {
|
) {
|
||||||
Some(ReportCombinedView::Comment(CommentReportView {
|
Some(ReportCombinedView::Comment(CommentReportView {
|
||||||
comment_report,
|
comment_report,
|
||||||
|
@ -384,7 +432,7 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||||
post,
|
post,
|
||||||
community,
|
community,
|
||||||
creator: v.report_creator,
|
creator: v.report_creator,
|
||||||
comment_creator: v.item_creator,
|
comment_creator,
|
||||||
creator_banned_from_community: v.item_creator_banned_from_community,
|
creator_banned_from_community: v.item_creator_banned_from_community,
|
||||||
creator_is_moderator: v.item_creator_is_moderator,
|
creator_is_moderator: v.item_creator_is_moderator,
|
||||||
creator_is_admin: v.item_creator_is_admin,
|
creator_is_admin: v.item_creator_is_admin,
|
||||||
|
@ -394,18 +442,32 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
|
||||||
my_vote: v.my_comment_vote,
|
my_vote: v.my_comment_vote,
|
||||||
resolver: v.resolver,
|
resolver: v.resolver,
|
||||||
}))
|
}))
|
||||||
} else if let (Some(private_message_report), Some(private_message)) =
|
} else if let (
|
||||||
(v.private_message_report, v.private_message)
|
Some(private_message_report),
|
||||||
|
Some(private_message),
|
||||||
|
Some(private_message_creator),
|
||||||
|
) = (v.private_message_report, v.private_message, v.item_creator)
|
||||||
{
|
{
|
||||||
Some(ReportCombinedView::PrivateMessage(
|
Some(ReportCombinedView::PrivateMessage(
|
||||||
PrivateMessageReportView {
|
PrivateMessageReportView {
|
||||||
private_message_report,
|
private_message_report,
|
||||||
private_message,
|
private_message,
|
||||||
creator: v.report_creator,
|
creator: v.report_creator,
|
||||||
private_message_creator: v.item_creator,
|
private_message_creator,
|
||||||
resolver: v.resolver,
|
resolver: v.resolver,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
} else if let (Some(community), Some(community_report), Some(counts)) =
|
||||||
|
(v.community, v.community_report, v.community_counts)
|
||||||
|
{
|
||||||
|
Some(ReportCombinedView::Community(CommunityReportView {
|
||||||
|
community_report,
|
||||||
|
community,
|
||||||
|
creator: v.report_creator,
|
||||||
|
counts,
|
||||||
|
subscribed: v.subscribed,
|
||||||
|
resolver: v.resolver,
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -433,6 +495,7 @@ mod tests {
|
||||||
comment::{Comment, CommentInsertForm},
|
comment::{Comment, CommentInsertForm},
|
||||||
comment_report::{CommentReport, CommentReportForm},
|
comment_report::{CommentReport, CommentReportForm},
|
||||||
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
|
||||||
|
community_report::{CommunityReport, CommunityReportForm},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
local_user::{LocalUser, LocalUserInsertForm},
|
local_user::{LocalUser, LocalUserInsertForm},
|
||||||
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
||||||
|
@ -558,6 +621,20 @@ mod tests {
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
let data = init_data(pool).await?;
|
||||||
|
|
||||||
|
// Sara reports the community
|
||||||
|
let sara_report_community_form = CommunityReportForm {
|
||||||
|
creator_id: data.sara.id,
|
||||||
|
community_id: data.community.id,
|
||||||
|
original_community_name: data.community.name.clone(),
|
||||||
|
original_community_title: data.community.title.clone(),
|
||||||
|
original_community_banner: None,
|
||||||
|
original_community_description: None,
|
||||||
|
original_community_sidebar: None,
|
||||||
|
original_community_icon: None,
|
||||||
|
reason: "from sara".into(),
|
||||||
|
};
|
||||||
|
CommunityReport::report(pool, &sara_report_community_form).await?;
|
||||||
|
|
||||||
// sara reports the post
|
// sara reports the post
|
||||||
let sara_report_post_form = PostReportForm {
|
let sara_report_post_form = PostReportForm {
|
||||||
creator_id: data.sara.id,
|
creator_id: data.sara.id,
|
||||||
|
@ -599,9 +676,14 @@ mod tests {
|
||||||
let reports = ReportCombinedQuery::default()
|
let reports = ReportCombinedQuery::default()
|
||||||
.list(pool, &data.admin_view)
|
.list(pool, &data.admin_view)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(3, reports.len());
|
assert_eq!(4, reports.len());
|
||||||
|
|
||||||
// Make sure the report types are correct
|
// Make sure the report types are correct
|
||||||
|
if let ReportCombinedView::Community(v) = &reports[3] {
|
||||||
|
assert_eq!(data.community.id, v.community.id);
|
||||||
|
} else {
|
||||||
|
panic!("wrong type");
|
||||||
|
}
|
||||||
if let ReportCombinedView::Post(v) = &reports[2] {
|
if let ReportCombinedView::Post(v) = &reports[2] {
|
||||||
assert_eq!(data.post.id, v.post.id);
|
assert_eq!(data.post.id, v.post.id);
|
||||||
assert_eq!(data.sara.id, v.creator.id);
|
assert_eq!(data.sara.id, v.creator.id);
|
||||||
|
@ -624,7 +706,7 @@ mod tests {
|
||||||
|
|
||||||
let report_count_admin =
|
let report_count_admin =
|
||||||
ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?;
|
ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?;
|
||||||
assert_eq!(3, report_count_admin);
|
assert_eq!(4, report_count_admin);
|
||||||
|
|
||||||
// Timmy should only see 2 reports, since they're not an admin,
|
// Timmy should only see 2 reports, since they're not an admin,
|
||||||
// but they do mod the community
|
// but they do mod the community
|
||||||
|
@ -971,4 +1053,62 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_community_reports() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool_for_tests();
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await?;
|
||||||
|
|
||||||
|
// jessica reports community
|
||||||
|
let community_report_form = CommunityReportForm {
|
||||||
|
creator_id: data.jessica.id,
|
||||||
|
community_id: data.community.id,
|
||||||
|
original_community_name: data.community.name.clone(),
|
||||||
|
original_community_title: data.community.title.clone(),
|
||||||
|
original_community_banner: None,
|
||||||
|
original_community_description: None,
|
||||||
|
original_community_sidebar: None,
|
||||||
|
original_community_icon: None,
|
||||||
|
reason: "the ice cream incident".into(),
|
||||||
|
};
|
||||||
|
let community_report = CommunityReport::report(pool, &community_report_form).await?;
|
||||||
|
|
||||||
|
let reports = ReportCombinedQuery::default()
|
||||||
|
.list(pool, &data.admin_view)
|
||||||
|
.await?;
|
||||||
|
assert_length!(1, reports);
|
||||||
|
if let ReportCombinedView::Community(v) = &reports[0] {
|
||||||
|
assert!(!v.community_report.resolved);
|
||||||
|
assert_eq!(data.jessica.name, v.creator.name);
|
||||||
|
assert_eq!(community_report.reason, v.community_report.reason);
|
||||||
|
assert_eq!(data.community.name, v.community.name);
|
||||||
|
assert_eq!(data.community.title, v.community.title);
|
||||||
|
} else {
|
||||||
|
panic!("wrong type");
|
||||||
|
}
|
||||||
|
|
||||||
|
// admin resolves the report (after taking appropriate action)
|
||||||
|
CommunityReport::resolve(pool, community_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::Community(v) = &reports[0] {
|
||||||
|
assert!(v.community_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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,18 @@ use diesel::Queryable;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types};
|
use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
aggregates::structs::{
|
||||||
|
CommentAggregates,
|
||||||
|
CommunityAggregates,
|
||||||
|
PersonAggregates,
|
||||||
|
PostAggregates,
|
||||||
|
SiteAggregates,
|
||||||
|
},
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
comment_report::CommentReport,
|
comment_report::CommentReport,
|
||||||
community::Community,
|
community::Community,
|
||||||
|
community_report::CommunityReport,
|
||||||
custom_emoji::CustomEmoji,
|
custom_emoji::CustomEmoji,
|
||||||
custom_emoji_keyword::CustomEmojiKeyword,
|
custom_emoji_keyword::CustomEmojiKeyword,
|
||||||
images::{ImageDetails, LocalImage},
|
images::{ImageDetails, LocalImage},
|
||||||
|
@ -80,6 +87,22 @@ pub struct CommentView {
|
||||||
pub my_vote: Option<i16>,
|
pub my_vote: Option<i16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// A community report view.
|
||||||
|
pub struct CommunityReportView {
|
||||||
|
pub community_report: CommunityReport,
|
||||||
|
pub community: Community,
|
||||||
|
pub creator: Person,
|
||||||
|
pub counts: CommunityAggregates,
|
||||||
|
pub subscribed: SubscribedType,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub resolver: Option<Person>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
@ -283,9 +306,12 @@ pub struct ReportCombinedViewInternal {
|
||||||
// Private-message-specific
|
// Private-message-specific
|
||||||
pub private_message_report: Option<PrivateMessageReport>,
|
pub private_message_report: Option<PrivateMessageReport>,
|
||||||
pub private_message: Option<PrivateMessage>,
|
pub private_message: Option<PrivateMessage>,
|
||||||
|
// Community-specific
|
||||||
|
pub community_report: Option<CommunityReport>,
|
||||||
|
pub community_counts: Option<CommunityAggregates>,
|
||||||
// Shared
|
// Shared
|
||||||
pub report_creator: Person,
|
pub report_creator: Person,
|
||||||
pub item_creator: Person,
|
pub item_creator: Option<Person>,
|
||||||
pub community: Option<Community>,
|
pub community: Option<Community>,
|
||||||
pub subscribed: SubscribedType,
|
pub subscribed: SubscribedType,
|
||||||
pub resolver: Option<Person>,
|
pub resolver: Option<Person>,
|
||||||
|
@ -304,6 +330,7 @@ pub enum ReportCombinedView {
|
||||||
Post(PostReportView),
|
Post(PostReportView),
|
||||||
Comment(CommentReportView),
|
Comment(CommentReportView),
|
||||||
PrivateMessage(PrivateMessageReportView),
|
PrivateMessage(PrivateMessageReportView),
|
||||||
|
Community(CommunityReportView),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
|
14
migrations/2024-12-27-220142_community_report/down.sql
Normal file
14
migrations/2024-12-27-220142_community_report/down.sql
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
DELETE FROM report_combined
|
||||||
|
WHERE community_report_id IS NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE report_combined
|
||||||
|
DROP CONSTRAINT report_combined_check,
|
||||||
|
ADD CHECK (num_nonnulls (post_report_id, comment_report_id, private_message_report_id) = 1),
|
||||||
|
DROP COLUMN community_report_id;
|
||||||
|
|
||||||
|
DROP TABLE community_report CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE community_aggregates
|
||||||
|
DROP COLUMN report_count,
|
||||||
|
DROP COLUMN unresolved_report_count;
|
||||||
|
|
29
migrations/2024-12-27-220142_community_report/up.sql
Normal file
29
migrations/2024-12-27-220142_community_report/up.sql
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
CREATE TABLE community_report (
|
||||||
|
id serial PRIMARY KEY,
|
||||||
|
creator_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
|
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
|
original_community_name text NOT NULL,
|
||||||
|
original_community_title text NOT NULL,
|
||||||
|
original_community_description text,
|
||||||
|
original_community_sidebar text,
|
||||||
|
original_community_icon text,
|
||||||
|
original_community_banner text,
|
||||||
|
reason text NOT NULL,
|
||||||
|
resolved bool NOT NULL DEFAULT FALSE,
|
||||||
|
resolver_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
published timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated timestamptz NULL,
|
||||||
|
UNIQUE (community_id, creator_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_community_report_published ON community_report (published DESC);
|
||||||
|
|
||||||
|
ALTER TABLE report_combined
|
||||||
|
ADD COLUMN community_report_id int UNIQUE REFERENCES community_report ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
DROP CONSTRAINT report_combined_check,
|
||||||
|
ADD CHECK (num_nonnulls (post_report_id, comment_report_id, private_message_report_id, community_report_id) = 1);
|
||||||
|
|
||||||
|
ALTER TABLE community_aggregates
|
||||||
|
ADD COLUMN report_count smallint NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN unresolved_report_count smallint NOT NULL DEFAULT 0;
|
||||||
|
|
Loading…
Reference in a new issue