diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index fe84802d7..c417ea2e4 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -89,19 +89,19 @@ pub struct PersonMentionId(i32); #[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", ts(export))] /// The comment report id. -pub struct CommentReportId(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 post report id. -pub struct PostReportId(i32); +pub struct PostReportId(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 private message report id. -pub struct PrivateMessageReportId(i32); +pub struct PrivateMessageReportId(pub i32); #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "full", derive(DieselNewType, TS))] diff --git a/crates/db_schema/src/source/combined/report.rs b/crates/db_schema/src/source/combined/report.rs index de0df8ad3..4085bddd6 100644 --- a/crates/db_schema/src/source/combined/report.rs +++ b/crates/db_schema/src/source/combined/report.rs @@ -2,6 +2,8 @@ use crate::newtypes::{CommentReportId, PostReportId, PrivateMessageReportId, Rep #[cfg(feature = "full")] use crate::schema::report_combined; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use i_love_jesus::CursorKeysModule; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -9,10 +11,14 @@ use ts_rs::TS; #[skip_serializing_none] #[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Selectable, TS))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, TS, CursorKeysModule) +)] #[cfg_attr(feature = "full", diesel(table_name = report_combined))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] +#[cfg_attr(feature = "full", cursor_keys_module(name = report_combined_keys))] /// A combined reports table. pub struct ReportCombined { pub id: ReportCombinedId, diff --git a/crates/db_views/src/report_combined_view.rs b/crates/db_views/src/report_combined_view.rs index 994361d80..4d0d3e342 100644 --- a/crates/db_views/src/report_combined_view.rs +++ b/crates/db_views/src/report_combined_view.rs @@ -3,6 +3,7 @@ use crate::structs::{ LocalUserView, PostReportView, PrivateMessageReportView, + ReportCombinedPaginationCursor, ReportCombinedView, ReportCombinedViewInternal, }; @@ -13,8 +14,10 @@ use diesel::{ JoinOnDsl, NullableExpressionMethods, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; +use i_love_jesus::PaginatedQueryBuilder; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, newtypes::CommunityId, @@ -36,8 +39,19 @@ use lemmy_db_schema::{ private_message_report, report_combined, }, - source::community::CommunityFollower, - utils::{actions, actions_alias, functions::coalesce, get_conn, limit_and_offset, DbPool}, + source::{ + combined::report::{report_combined_keys as key, ReportCombined}, + community::CommunityFollower, + }, + utils::{ + actions, + actions_alias, + functions::coalesce, + get_conn, + limit_and_offset, + DbPool, + ReverseTimestampKey, + }, }; use lemmy_utils::error::LemmyResult; @@ -94,12 +108,47 @@ impl ReportCombinedViewInternal { } } +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 { + 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, pub page: Option, pub limit: Option, pub unresolved_only: bool, + pub page_after: Option, + pub page_back: bool, } impl ReportCombinedQuery { @@ -237,18 +286,6 @@ impl ReportCombinedQuery { query = query.filter(community::id.eq(community_id)); } - // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest - // first (FIFO) - if options.unresolved_only { - query = query - .filter(post_report::resolved.eq(false)) - .or_filter(comment_report::resolved.eq(false)) - .or_filter(private_message_report::resolved.eq(false)) - .order_by(report_combined::published.asc()); - } else { - query = query.order_by(report_combined::published.desc()); - } - // 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()); @@ -258,6 +295,36 @@ impl ReportCombinedQuery { query = query.limit(limit).offset(offset); + let mut query = PaginatedQueryBuilder::new(query); + + let page_after = options.page_after.map(|c| c.0); + + if options.page_back { + 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 options.unresolved_only { + query = query + .filter( + post_report::resolved + .eq(false) + .or(comment_report::resolved.eq(false)) + .or(private_message_report::resolved.eq(false)), + ) + // 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::(conn).await?; // Map the query results to the enum diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 2ce42fd18..cb80c5a2d 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -126,6 +126,12 @@ pub struct PostReportView { #[cfg_attr(feature = "full", ts(export))] pub struct PaginationCursor(pub String); +/// like PaginationCursor but for the report_combined table +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(ts_rs::TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct ReportCombinedPaginationCursor(pub String); + #[skip_serializing_none] #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "full", derive(TS, Queryable))] diff --git a/migrations/2024-11-26-115042_add_combined_tables/up.sql b/migrations/2024-11-26-115042_add_combined_tables/up.sql index 195856fcc..ef7900ddd 100644 --- a/migrations/2024-11-26-115042_add_combined_tables/up.sql +++ b/migrations/2024-11-26-115042_add_combined_tables/up.sql @@ -17,7 +17,9 @@ CREATE TABLE report_combined ( UNIQUE (post_report_id, comment_report_id, private_message_report_id) ); -CREATE INDEX idx_report_combined_published ON report_combined (published DESC); +CREATE INDEX idx_report_combined_published ON report_combined (published DESC, id DESC); + +CREATE INDEX idx_report_combined_published_asc ON report_combined (reverse_timestamp_sort (published) DESC, id DESC); -- Updating the history INSERT INTO report_combined (published, post_report_id)