diff --git a/config/defaults.hjson b/config/defaults.hjson index b5d3b1004..4922d74aa 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -110,7 +110,11 @@ bind: "127.0.0.1" port: 10002 } - # Sets a response Access-Control-Allow-Origin CORS header + # Sets a response Access-Control-Allow-Origin CORS header. Can also be set via environment: + # `LEMMY_CORS_ORIGIN=example.org,site.com` # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - cors_origin: "lemmy.tld" + cors_origin: [ + "lemmy.tld" + /* ... */ + ] } diff --git a/crates/api/src/reports/report_combined/list.rs b/crates/api/src/reports/report_combined/list.rs index 16b48c619..646d985d2 100644 --- a/crates/api/src/reports/report_combined/list.rs +++ b/crates/api/src/reports/report_combined/list.rs @@ -15,9 +15,6 @@ pub async fn list_reports( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let community_id = data.community_id; - let unresolved_only = data.unresolved_only; - check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?; // parse pagination token @@ -29,8 +26,10 @@ pub async fn list_reports( let page_back = data.page_back; let reports = ReportCombinedQuery { - community_id, - unresolved_only, + community_id: data.community_id, + post_id: data.post_id, + type_: data.type_, + unresolved_only: data.unresolved_only, page_after, page_back, } diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 3d8cce9ee..7eac9b21e 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -33,7 +33,7 @@ use std::{cmp::min, time::Duration}; #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(ts_rs::TS))] #[cfg_attr(feature = "full", ts(export))] -/// Saves settings for your user. +/// A response that completes successfully. pub struct SuccessResponse { pub success: bool, } diff --git a/crates/api_common/src/reports/combined.rs b/crates/api_common/src/reports/combined.rs index 69d928830..cd87c8a3d 100644 --- a/crates/api_common/src/reports/combined.rs +++ b/crates/api_common/src/reports/combined.rs @@ -1,4 +1,7 @@ -use lemmy_db_schema::newtypes::CommunityId; +use lemmy_db_schema::{ + newtypes::{CommunityId, PostId}, + ReportType, +}; use lemmy_db_views::structs::{ReportCombinedPaginationCursor, ReportCombinedView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -14,6 +17,12 @@ pub struct ListReports { /// Only shows the unresolved reports #[cfg_attr(feature = "full", ts(optional))] pub unresolved_only: Option, + /// Filter the type of report. + #[cfg_attr(feature = "full", ts(optional))] + pub type_: Option, + /// Filter by the post id. Can return either comment or post reports. + #[cfg_attr(feature = "full", ts(optional))] + pub post_id: Option, /// if no community is given, it returns reports for all communities moderated by the auth user #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 08678e5d0..980e6ac8d 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -245,13 +245,25 @@ pub enum InboxDataType { #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] -/// A list of possible types for the various modlog actions. +/// A list of possible types for a person's content. pub enum PersonContentType { All, Comments, Posts, } +#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A list of possible types for reports. +pub enum ReportType { + All, + Posts, + Comments, + PrivateMessages, + Communities, +} + #[derive( EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash, )] diff --git a/crates/db_views/src/combined/report_combined_view.rs b/crates/db_views/src/combined/report_combined_view.rs index fb35df0cd..0fce1afcb 100644 --- a/crates/db_views/src/combined/report_combined_view.rs +++ b/crates/db_views/src/combined/report_combined_view.rs @@ -22,7 +22,7 @@ use diesel_async::RunQueryDsl; use i_love_jesus::PaginatedQueryBuilder; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, - newtypes::CommunityId, + newtypes::{CommunityId, PostId}, schema::{ comment, comment_actions, @@ -49,6 +49,7 @@ use lemmy_db_schema::{ }, traits::InternalToCombinedView, utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey}, + ReportType, }; use lemmy_utils::error::LemmyResult; @@ -148,6 +149,8 @@ pub struct PaginationCursorData(ReportCombined); #[derive(Default)] pub struct ReportCombinedQuery { + pub type_: Option, + pub post_id: Option, pub community_id: Option, pub unresolved_only: Option, pub page_after: Option, @@ -167,6 +170,33 @@ impl ReportCombinedQuery { let conn = &mut get_conn(pool).await?; + let report_creator_join = post_report::creator_id + .eq(report_creator) + .or(comment_report::creator_id.eq(report_creator)) + .or(private_message_report::creator_id.eq(report_creator)) + .or(community_report::creator_id.eq(report_creator)); + + let item_creator_join = post::creator_id + .eq(item_creator) + .or(comment::creator_id.eq(item_creator)) + .or(private_message::creator_id.eq(item_creator)); + + let resolver_join = private_message_report::resolver_id + .eq(resolver) + .or(post_report::resolver_id.eq(resolver)) + .or(comment_report::resolver_id.eq(resolver)) + .or(community_report::resolver_id.eq(resolver)); + + let post_join = post_report::post_id + .eq(post::id) + .or(comment::post_id.eq(post::id)); + + let community_join = community::table.on( + community_report::community_id + .eq(community::id) + .or(post::community_id.eq(community::id)), + ); + // 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: @@ -178,15 +208,7 @@ impl ReportCombinedQuery { .left_join(private_message_report::table) .left_join(community_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)) - .or(community_report::creator_id.eq(report_creator)), - ), - ) + .inner_join(person::table.on(report_creator_join)) // The comment .left_join(comment::table.on(comment_report::comment_id.eq(comment::id))) // The private message @@ -195,30 +217,13 @@ impl ReportCombinedQuery { .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)), - ), - ) + .left_join(post::table.on(post_join)) // The item creator (`item_creator` is the id of this person) - .left_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)), - ), - ) + .left_join(aliases::person1.on(item_creator_join)) + // The resolver + .left_join(aliases::person2.on(resolver_join)) // The community - .left_join( - community::table.on( - post::community_id - .eq(community::id) - .or(community_report::community_id.eq(community::id)), - ), - ) + .left_join(community_join) .left_join(actions_alias( creator_community_actions, item_creator, @@ -250,16 +255,6 @@ impl ReportCombinedQuery { community_aggregates::table .on(community_report::community_id.eq(community_aggregates::community_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)) - .or(community_report::resolver_id.eq(resolver)), - ), - ) .left_join(actions( comment_actions::table, Some(my_person_id), @@ -318,6 +313,10 @@ impl ReportCombinedQuery { ); } + if let Some(post_id) = self.post_id { + query = query.filter(post::id.eq(post_id)); + } + // If its not an admin, get only the ones you mod if !user.local_user.admin { query = query.filter( @@ -337,6 +336,18 @@ impl ReportCombinedQuery { query = query.after(page_after); } + if let Some(type_) = self.type_ { + query = match type_ { + ReportType::All => query, + ReportType::Posts => query.filter(report_combined::post_report_id.is_not_null()), + ReportType::Comments => query.filter(report_combined::comment_report_id.is_not_null()), + ReportType::PrivateMessages => { + query.filter(report_combined::private_message_report_id.is_not_null()) + } + ReportType::Communities => query.filter(report_combined::community_report_id.is_not_null()), + } + } + // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest // first (FIFO) if self.unresolved_only.unwrap_or_default() { @@ -508,6 +519,7 @@ mod tests { }, traits::{Crud, Joinable, Reportable}, utils::{build_db_pool_for_tests, DbPool}, + ReportType, }; use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; @@ -677,7 +689,7 @@ mod tests { let reports = ReportCombinedQuery::default() .list(pool, &data.admin_view) .await?; - assert_eq!(4, reports.len()); + assert_length!(4, reports); // Make sure the report types are correct if let ReportCombinedView::Community(v) = &reports[3] { @@ -709,22 +721,41 @@ mod tests { ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?; assert_eq!(4, report_count_admin); + // Make sure the type_ filter is working + let reports_by_type = ReportCombinedQuery { + type_: Some(ReportType::Posts), + ..Default::default() + } + .list(pool, &data.admin_view) + .await?; + assert_length!(1, reports_by_type); + + // Filter by the post id + // Should be 2, for the post, and the comment on that post + let reports_by_post_id = ReportCombinedQuery { + post_id: Some(data.post.id), + ..Default::default() + } + .list(pool, &data.admin_view) + .await?; + assert_length!(2, reports_by_post_id); + // Timmy should only see 2 reports, since they're not an admin, // but they do mod the community - let reports = ReportCombinedQuery::default() + let timmys_reports = ReportCombinedQuery::default() .list(pool, &data.timmy_view) .await?; - assert_eq!(2, reports.len()); + assert_length!(2, timmys_reports); // Make sure the report types are correct - if let ReportCombinedView::Post(v) = &reports[1] { + if let ReportCombinedView::Post(v) = &timmys_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] { + if let ReportCombinedView::Comment(v) = &timmys_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); @@ -1050,6 +1081,16 @@ mod tests { ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?; assert_eq!(1, report_count_after_resolved); + // Filter by post id, which should still include the comments. + let reports_post_id_filter = ReportCombinedQuery { + post_id: Some(data.post.id), + ..Default::default() + } + .list(pool, &data.timmy_view) + .await?; + + assert_length!(2, reports_post_id_filter); + cleanup(data, pool).await?; Ok(()) diff --git a/crates/routes/src/utils/mod.rs b/crates/routes/src/utils/mod.rs index ccb363df9..632905c90 100644 --- a/crates/routes/src/utils/mod.rs +++ b/crates/routes/src/utils/mod.rs @@ -9,30 +9,19 @@ pub fn cors_config(settings: &Settings) -> Cors { let self_origin = settings.get_protocol_and_hostname(); let cors_origin_setting = settings.cors_origin(); - // A default setting for either wildcard, or None - let cors_default = Cors::default() - .allow_any_origin() + let mut cors = Cors::default() .allow_any_method() .allow_any_header() .expose_any_header() .max_age(3600); - match (cors_origin_setting.clone(), cfg!(debug_assertions)) { - (Some(origin), false) => { - // Need to call send_wildcard() explicitly, passing this into allowed_origin() results in - // error - if origin == "*" { - cors_default - } else { - Cors::default() - .allowed_origin(&origin) - .allowed_origin(&self_origin) - .allow_any_method() - .allow_any_header() - .expose_any_header() - .max_age(3600) - } + if cfg!(debug_assertions) || cors_origin_setting.contains(&"*".to_string()) { + cors = cors.allow_any_origin(); + } else { + cors = cors.allowed_origin(&self_origin); + for c in cors_origin_setting { + cors = cors.allowed_origin(&c); } - _ => cors_default, } + cors } diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index effd68a64..4577f4e60 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -44,17 +44,19 @@ pub struct Settings { // Prometheus configuration. #[doku(example = "Some(Default::default())")] pub prometheus: Option, - /// Sets a response Access-Control-Allow-Origin CORS header + /// Sets a response Access-Control-Allow-Origin CORS header. Can also be set via environment: + /// `LEMMY_CORS_ORIGIN=example.org,site.com` /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin #[doku(example = "lemmy.tld")] - cors_origin: Option, + cors_origin: Vec, } impl Settings { - pub fn cors_origin(&self) -> Option { + pub fn cors_origin(&self) -> Vec { env::var("LEMMY_CORS_ORIGIN") .ok() - .or(self.cors_origin.clone()) + .map(|e| e.split(',').map(ToString::to_string).collect()) + .unwrap_or(self.cors_origin.clone()) } }