Extracting pagination cursor utils into a trait. (#5424)

* Extracting pagination cursor utils into a trait.

- Fixes #5275

* Refactoring to avoid stack overflows.

* Fixing api_common feature.

* Rename the traits and paginationcursor::new

* Using combined trait.

* Removing empty files.
This commit is contained in:
Dessalines 2025-03-04 13:00:18 -05:00 committed by GitHub
parent 2f2f58da65
commit a6507c169d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 615 additions and 509 deletions

View file

@ -5,9 +5,10 @@ use lemmy_api_common::{
person::{ListPersonSaved, ListPersonSavedResponse},
utils::check_private_instance,
};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::person_saved_combined_view::PersonSavedCombinedQuery,
structs::{LocalUserView, SiteView},
structs::{LocalUserView, PersonSavedCombinedView, SiteView},
};
use lemmy_utils::error::LemmyResult;
@ -20,22 +21,21 @@ pub async fn list_person_saved(
check_private_instance(&Some(local_user_view.clone()), &local_site.local_site)?;
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(PersonSavedCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let type_ = data.type_;
let saved = PersonSavedCombinedQuery {
type_,
page_after,
page_back,
type_: data.type_,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(ListPersonSavedResponse { saved }))
let next_page = saved.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListPersonSavedResponse { saved, next_page }))
}

View file

@ -3,7 +3,11 @@ use lemmy_api_common::{
context::LemmyContext,
person::{ListInbox, ListInboxResponse},
};
use lemmy_db_views::{combined::inbox_combined_view::InboxCombinedQuery, structs::LocalUserView};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::inbox_combined_view::InboxCombinedQuery,
structs::{InboxCombinedView, LocalUserView},
};
use lemmy_utils::error::LemmyResult;
pub async fn list_inbox(
@ -11,28 +15,25 @@ pub async fn list_inbox(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListInboxResponse>> {
let unread_only = data.unread_only;
let type_ = data.type_;
let person_id = local_user_view.person.id;
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(InboxCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let inbox = InboxCombinedQuery {
type_,
unread_only,
show_bot_accounts,
page_after,
page_back,
type_: data.type_,
unread_only: data.unread_only,
show_bot_accounts: Some(local_user_view.local_user.show_bot_accounts),
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), person_id)
.await?;
Ok(Json(ListInboxResponse { inbox }))
let next_page = inbox.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListInboxResponse { inbox, next_page }))
}

View file

@ -4,7 +4,11 @@ use lemmy_api_common::{
reports::combined::{ListReports, ListReportsResponse},
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::{combined::report_combined_view::ReportCombinedQuery, structs::LocalUserView};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::report_combined_view::ReportCombinedQuery,
structs::{LocalUserView, ReportCombinedView},
};
use lemmy_utils::error::LemmyResult;
/// Lists reports for a community if an id is supplied
@ -21,26 +25,26 @@ pub async fn list_reports(
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
}
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(ReportCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let reports = ReportCombinedQuery {
community_id: data.community_id,
post_id: data.post_id,
type_: data.type_,
unresolved_only: data.unresolved_only,
page_after,
page_back,
cursor_data,
page_back: data.page_back,
show_community_rule_violations: data.show_community_rule_violations,
my_reports_only,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(ListReportsResponse { reports }))
let next_page = reports.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListReportsResponse { reports, next_page }))
}

View file

@ -4,8 +4,11 @@ use lemmy_api_common::{
site::{GetModlog, GetModlogResponse},
utils::{check_community_mod_of_any_or_admin_action, check_private_instance},
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::{combined::modlog_combined_view::ModlogCombinedQuery, structs::LocalUserView};
use lemmy_db_schema::{source::local_site::LocalSite, traits::PaginationCursorBuilder};
use lemmy_db_views::{
combined::modlog_combined_view::ModlogCombinedQuery,
structs::{LocalUserView, ModlogCombinedView},
};
use lemmy_utils::error::LemmyResult;
pub async fn get_mod_log(
@ -17,10 +20,6 @@ pub async fn get_mod_log(
check_private_instance(&local_user_view, &local_site)?;
let type_ = data.type_;
let listing_type = data.listing_type;
let community_id = data.community_id;
let is_mod_or_admin = if let Some(local_user_view) = &local_user_view {
check_community_mod_of_any_or_admin_action(local_user_view, &mut context.pool())
.await
@ -35,34 +34,30 @@ pub async fn get_mod_log(
} else {
data.mod_person_id
};
let other_person_id = data.other_person_id;
let post_id = data.post_id;
let comment_id = data.comment_id;
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(ModlogCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let modlog = ModlogCombinedQuery {
type_,
listing_type,
community_id,
type_: data.type_,
listing_type: data.listing_type,
community_id: data.community_id,
mod_person_id,
other_person_id,
local_user,
post_id,
comment_id,
other_person_id: data.other_person_id,
local_user: local_user_view.as_ref().map(|u| &u.local_user),
post_id: data.post_id,
comment_id: data.comment_id,
hide_modlog_names: Some(hide_modlog_names),
page_after,
page_back,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool())
.await?;
Ok(Json(GetModlogResponse { modlog }))
let next_page = modlog.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(GetModlogResponse { modlog, next_page }))
}

View file

@ -3,6 +3,7 @@ use lemmy_db_schema::{
CommentReplyId,
CommunityId,
LanguageId,
PaginationCursor,
PersonCommentMentionId,
PersonId,
PersonPostMentionId,
@ -18,12 +19,10 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::{
CommunityModeratorView,
InboxCombinedPaginationCursor,
InboxCombinedView,
LocalImageView,
PersonContentCombinedPaginationCursor,
PersonContentCombinedView,
PersonSavedCombinedPaginationCursor,
PersonSavedCombinedView,
PersonView,
};
use serde::{Deserialize, Serialize};
@ -264,7 +263,7 @@ pub struct ListPersonContent {
#[cfg_attr(feature = "full", ts(optional))]
pub username: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PersonContentCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -276,6 +275,9 @@ pub struct ListPersonContent {
/// A person's content response.
pub struct ListPersonContentResponse {
pub content: Vec<PersonContentCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[skip_serializing_none]
@ -287,7 +289,7 @@ pub struct ListPersonSaved {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<PersonContentType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PersonSavedCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -298,7 +300,10 @@ pub struct ListPersonSaved {
#[cfg_attr(feature = "full", ts(export))]
/// A person's saved content response.
pub struct ListPersonSavedResponse {
pub saved: Vec<PersonContentCombinedView>,
pub saved: Vec<PersonSavedCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq)]
@ -386,7 +391,7 @@ pub struct ListInbox {
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<InboxCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -397,6 +402,9 @@ pub struct ListInbox {
/// Get your inbox (replies, comment mentions, post mentions, and messages)
pub struct ListInboxResponse {
pub inbox: Vec<InboxCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]

View file

@ -4,7 +4,7 @@ use lemmy_db_schema::{
PostFeatureType,
PostSortType,
};
use lemmy_db_views::structs::{CommunityView, PaginationCursor, PostView, VoteView};
use lemmy_db_views::structs::{CommunityView, PostPaginationCursor, PostView, VoteView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -125,7 +125,7 @@ pub struct GetPosts {
/// If true, then only show posts with no comments
pub no_comments_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PaginationCursor>,
pub page_cursor: Option<PostPaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -139,7 +139,7 @@ pub struct GetPostsResponse {
pub posts: Vec<PostView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
pub next_page: Option<PostPaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]

View file

@ -1,8 +1,8 @@
use lemmy_db_schema::{
newtypes::{CommunityId, PostId},
newtypes::{CommunityId, PaginationCursor, PostId},
ReportType,
};
use lemmy_db_views::structs::{ReportCombinedPaginationCursor, ReportCombinedView};
use lemmy_db_views::structs::ReportCombinedView;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -27,7 +27,7 @@ pub struct ListReports {
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<ReportCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
/// Only for admins: also show reports with `violates_instance_rules=false`
@ -44,4 +44,7 @@ pub struct ListReports {
/// The post reports response.
pub struct ListReportsResponse {
pub reports: Vec<ReportCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}

View file

@ -6,6 +6,7 @@ use lemmy_db_schema::{
CommunityId,
InstanceId,
LanguageId,
PaginationCursor,
PersonId,
PostId,
RegistrationApplicationId,
@ -36,12 +37,10 @@ use lemmy_db_views::structs::{
CommunityModeratorView,
CommunityView,
LocalUserView,
ModlogCombinedPaginationCursor,
ModlogCombinedView,
PersonView,
PostView,
RegistrationApplicationView,
SearchCombinedPaginationCursor,
SearchCombinedView,
SiteView,
};
@ -83,7 +82,7 @@ pub struct Search {
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<SearchCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -94,6 +93,9 @@ pub struct Search {
/// The search response, containing lists of the return type possibilities
pub struct SearchResponse {
pub results: Vec<SearchCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
@ -151,7 +153,7 @@ pub struct GetModlog {
#[cfg_attr(feature = "full", ts(optional))]
pub comment_id: Option<CommentId>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<ModlogCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -162,6 +164,9 @@ pub struct GetModlog {
/// The modlog fetch response.
pub struct GetModlogResponse {
pub modlog: Vec<ModlogCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[skip_serializing_none]

View file

@ -6,9 +6,10 @@ use lemmy_api_common::{
person::{ListPersonContent, ListPersonContentResponse},
utils::check_private_instance,
};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::person_content_combined_view::PersonContentCombinedQuery,
structs::{LocalUserView, SiteView},
structs::{LocalUserView, PersonContentCombinedView, SiteView},
};
use lemmy_utils::error::LemmyResult;
@ -29,23 +30,22 @@ pub async fn list_person_content(
)
.await?;
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(PersonContentCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let type_ = data.type_;
let content = PersonContentCombinedQuery {
creator_id: person_details_id,
type_,
page_after,
page_back,
type_: data.type_,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(ListPersonContentResponse { content }))
let next_page = content.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListPersonContentResponse { content, next_page }))
}

View file

@ -20,7 +20,7 @@ use lemmy_db_schema::{
};
use lemmy_db_views::{
post::post_view::PostQuery,
structs::{LocalUserView, PaginationCursor, SiteView},
structs::{LocalUserView, PostPaginationCursor, SiteView},
};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
@ -116,6 +116,6 @@ pub async fn list_posts(
}
// if this page wasn't empty, then there is a next page after the last post on this page
let next_page = posts.last().map(PaginationCursor::after_post);
let next_page = posts.last().map(PostPaginationCursor::after_post);
Ok(Json(GetPostsResponse { posts, next_page }))
}

View file

@ -6,10 +6,10 @@ use lemmy_api_common::{
site::{Search, SearchResponse},
utils::{check_conflicting_like_filters, check_private_instance},
};
use lemmy_db_schema::source::community::Community;
use lemmy_db_schema::{source::community::Community, traits::PaginationCursorBuilder};
use lemmy_db_views::{
combined::search_combined_view::SearchCombinedQuery,
structs::{LocalUserView, SiteView},
structs::{LocalUserView, SearchCombinedView, SiteView},
};
use lemmy_utils::error::LemmyResult;
@ -32,34 +32,32 @@ pub async fn search(
} else {
data.community_id
};
let search_term = data.search_term.clone();
let time_range_seconds = data.time_range_seconds;
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(SearchCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let results = SearchCombinedQuery {
search_term,
search_term: data.search_term.clone(),
community_id,
creator_id: data.creator_id,
type_: data.type_,
sort: data.sort,
time_range_seconds,
time_range_seconds: data.time_range_seconds,
listing_type: data.listing_type,
title_only: data.title_only,
post_url_only: data.post_url_only,
liked_only: data.liked_only,
disliked_only: data.disliked_only,
page_after,
page_back,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(SearchResponse { results }))
let next_page = results.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(SearchResponse { results, next_page }))
}

View file

@ -22,7 +22,7 @@ use lemmy_db_schema::{
utils::{build_db_pool, get_conn, now},
PostSortType,
};
use lemmy_db_views::{post::post_view::PostQuery, structs::PaginationCursor};
use lemmy_db_views::{post::post_view::PostQuery, structs::PostPaginationCursor};
use lemmy_utils::error::{LemmyErrorExt2, LemmyResult};
use std::num::NonZeroU32;
use url::Url;
@ -162,7 +162,7 @@ async fn try_main() -> LemmyResult<()> {
if let Some(post_view) = post_views.into_iter().next_back() {
println!("👀 getting pagination cursor data for next page");
let cursor_data = PaginationCursor::after_post(&post_view)
let cursor_data = PostPaginationCursor::after_post(&post_view)
.read(&mut conn.into(), None)
.await?;
page_after = Some(cursor_data);

View file

@ -1,5 +1,4 @@
use crate::{
diesel::{BoolExpressionMethods, NullableExpressionMethods, OptionalExtension},
newtypes::{CommunityId, DbUrl, PersonId, PostId},
schema::{community, person, post, post_actions},
source::post::{
@ -22,6 +21,7 @@ use crate::{
get_conn,
now,
uplete,
DbConn,
DbPool,
DELETED_REPLACEMENT_TEXT,
FETCH_LIMIT_MAX,
@ -35,8 +35,11 @@ use diesel::{
dsl::{count, insert_into, not},
expression::SelectableHelper,
result::Error,
BoolExpressionMethods,
DecoratableTarget,
ExpressionMethods,
NullableExpressionMethods,
OptionalExtension,
QueryDsl,
TextExpressionMethods,
};
@ -436,6 +439,14 @@ impl PostActionsCursor {
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
Self::read_conn(conn, post_id, person_id).await
}
pub async fn read_conn(
conn: &mut DbConn<'_>,
post_id: PostId,
person_id: Option<PersonId>,
) -> Result<Self, Error> {
Ok(if let Some(person_id) = person_id {
post_actions::table
.find((person_id, post_id))

View file

@ -15,6 +15,8 @@ use diesel::{
};
#[cfg(feature = "full")]
use diesel_ltree::Ltree;
#[cfg(feature = "full")]
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize};
use std::{
fmt,
@ -420,3 +422,31 @@ impl InstanceId {
#[cfg_attr(feature = "full", ts(export))]
/// The internal tag id.
pub struct TagId(pub i32);
/// A pagination cursor
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct PaginationCursor(pub String);
#[cfg(feature = "full")]
impl PaginationCursor {
pub fn new(prefix: char, id: i32) -> Self {
// hex encoding to prevent ossification
Self(format!("{prefix}{id:x}"))
}
pub fn prefix_and_id(&self) -> LemmyResult<(char, i32)> {
let (prefix_str, id_str) = self
.0
.split_at_checked(1)
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
let prefix = prefix_str
.chars()
.next()
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
let id = i32::from_str_radix(id_str, 16)?;
Ok((prefix, id))
}
}

View file

@ -1,5 +1,5 @@
use crate::{
newtypes::{CommunityId, DbUrl, PersonId},
newtypes::{CommunityId, DbUrl, PaginationCursor, PersonId},
utils::{get_conn, uplete, DbPool},
};
use diesel::{
@ -257,3 +257,18 @@ pub trait InternalToCombinedView {
/// Maps the combined DB row to an enum
fn map_to_enum(self) -> Option<Self::CombinedView>;
}
pub trait PaginationCursorBuilder {
type CursorData;
/// Builds a pagination cursor for the given query result.
fn to_cursor(&self) -> PaginationCursor;
/// Reads a database row from a given pagination cursor.
fn from_cursor(
cursor: &PaginationCursor,
conn: &mut DbPool<'_>,
) -> impl Future<Output = LemmyResult<Self::CursorData>> + Send
where
Self: Sized;
}

View file

@ -1,6 +1,5 @@
use crate::structs::{
CommentReplyView,
InboxCombinedPaginationCursor,
InboxCombinedView,
InboxCombinedViewInternal,
PersonCommentMentionView,
@ -22,7 +21,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{self, creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::PersonId,
newtypes::{PaginationCursor, PersonId},
schema::{
comment,
comment_actions,
@ -46,11 +45,11 @@ use lemmy_db_schema::{
tag,
},
source::combined::inbox::{inbox_combined_keys as key, InboxCombined},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{functions::coalesce, get_conn, DbPool},
InboxDataType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
impl InboxCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
@ -218,48 +217,49 @@ impl InboxCombinedViewInternal {
}
}
impl InboxCombinedPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &InboxCombinedView) -> InboxCombinedPaginationCursor {
let (prefix, id) = match view {
impl PaginationCursorBuilder for InboxCombinedView {
type CursorData = InboxCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
InboxCombinedView::CommentReply(v) => ('R', v.comment_reply.id.0),
InboxCombinedView::CommentMention(v) => ('C', v.person_comment_mention.id.0),
InboxCombinedView::PostMention(v) => ('P', v.person_post_mention.id.0),
InboxCombinedView::PrivateMessage(v) => ('M', v.private_message.id.0),
};
// hex encoding to prevent ossification
InboxCombinedPaginationCursor(format!("{prefix}{id:x}"))
PaginationCursor::new(prefix, id)
}
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 = inbox_combined::table
.select(InboxCombined::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 {
"R" => query.filter(inbox_combined::comment_reply_id.eq(id)),
"C" => query.filter(inbox_combined::person_comment_mention_id.eq(id)),
"P" => query.filter(inbox_combined::person_post_mention_id.eq(id)),
"M" => query.filter(inbox_combined::private_message_id.eq(id)),
_ => return Err(err_msg()),
};
let token = query.first(&mut get_conn(pool).await?).await?;
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
Ok(PaginationCursorData(token))
let mut query = inbox_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
query = match prefix {
'R' => query.filter(inbox_combined::comment_reply_id.eq(id)),
'C' => query.filter(inbox_combined::person_comment_mention_id.eq(id)),
'P' => query.filter(inbox_combined::person_post_mention_id.eq(id)),
'M' => query.filter(inbox_combined::private_message_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
#[derive(Clone)]
pub struct PaginationCursorData(InboxCombined);
#[derive(Default)]
pub struct InboxCombinedQuery {
pub type_: Option<InboxDataType>,
pub unread_only: Option<bool>,
pub show_bot_accounts: Option<bool>,
pub page_after: Option<PaginationCursorData>,
pub cursor_data: Option<InboxCombined>,
pub page_back: Option<bool>,
}
@ -397,12 +397,10 @@ impl InboxCombinedQuery {
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
// Sorting by published

View file

@ -16,12 +16,10 @@ use crate::structs::{
ModRemoveCommunityView,
ModRemovePostView,
ModTransferCommunityView,
ModlogCombinedPaginationCursor,
ModlogCombinedView,
ModlogCombinedViewInternal,
};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
IntoSql,
@ -35,7 +33,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases,
impls::local_user::LocalUserOptionHelper,
newtypes::{CommentId, CommunityId, PersonId, PostId},
newtypes::{CommentId, CommunityId, PaginationCursor, PersonId, PostId},
schema::{
admin_allow_instance,
admin_block_instance,
@ -66,12 +64,13 @@ use lemmy_db_schema::{
combined::modlog::{modlog_combined_keys as key, ModlogCombined},
local_user::LocalUser,
},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{get_conn, DbPool},
ListingType,
ModlogActionType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
impl ModlogCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
fn joins(
@ -228,81 +227,69 @@ impl ModlogCombinedViewInternal {
}
}
impl ModlogCombinedPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &ModlogCombinedView) -> ModlogCombinedPaginationCursor {
let (prefix, id) = match view {
ModlogCombinedView::AdminAllowInstance(v) => {
("AdminAllowInstance", v.admin_allow_instance.id.0)
}
ModlogCombinedView::AdminBlockInstance(v) => {
("AdminBlockInstance", v.admin_block_instance.id.0)
}
ModlogCombinedView::AdminPurgeComment(v) => ("AdminPurgeComment", v.admin_purge_comment.id.0),
ModlogCombinedView::AdminPurgeCommunity(v) => {
("AdminPurgeCommunity", v.admin_purge_community.id.0)
}
ModlogCombinedView::AdminPurgePerson(v) => ("AdminPurgePerson", v.admin_purge_person.id.0),
ModlogCombinedView::AdminPurgePost(v) => ("AdminPurgePost", v.admin_purge_post.id.0),
ModlogCombinedView::ModAdd(v) => ("ModAdd", v.mod_add.id.0),
ModlogCombinedView::ModAddCommunity(v) => ("ModAddCommunity", v.mod_add_community.id.0),
ModlogCombinedView::ModBan(v) => ("ModBan", v.mod_ban.id.0),
ModlogCombinedView::ModBanFromCommunity(v) => {
("ModBanFromCommunity", v.mod_ban_from_community.id.0)
}
ModlogCombinedView::ModFeaturePost(v) => ("ModFeaturePost", v.mod_feature_post.id.0),
ModlogCombinedView::ModHideCommunity(v) => ("ModHideCommunity", v.mod_hide_community.id.0),
ModlogCombinedView::ModLockPost(v) => ("ModLockPost", v.mod_lock_post.id.0),
ModlogCombinedView::ModRemoveComment(v) => ("ModRemoveComment", v.mod_remove_comment.id.0),
ModlogCombinedView::ModRemoveCommunity(v) => {
("ModRemoveCommunity", v.mod_remove_community.id.0)
}
ModlogCombinedView::ModRemovePost(v) => ("ModRemovePost", v.mod_remove_post.id.0),
ModlogCombinedView::ModTransferCommunity(v) => {
("ModTransferCommunity", v.mod_transfer_community.id.0)
}
impl PaginationCursorBuilder for ModlogCombinedView {
type CursorData = ModlogCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
ModlogCombinedView::AdminAllowInstance(v) => ('A', v.admin_allow_instance.id.0),
ModlogCombinedView::AdminBlockInstance(v) => ('B', v.admin_block_instance.id.0),
ModlogCombinedView::AdminPurgeComment(v) => ('C', v.admin_purge_comment.id.0),
ModlogCombinedView::AdminPurgeCommunity(v) => ('D', v.admin_purge_community.id.0),
ModlogCombinedView::AdminPurgePerson(v) => ('E', v.admin_purge_person.id.0),
ModlogCombinedView::AdminPurgePost(v) => ('F', v.admin_purge_post.id.0),
ModlogCombinedView::ModAdd(v) => ('G', v.mod_add.id.0),
ModlogCombinedView::ModAddCommunity(v) => ('H', v.mod_add_community.id.0),
ModlogCombinedView::ModBan(v) => ('I', v.mod_ban.id.0),
ModlogCombinedView::ModBanFromCommunity(v) => ('J', v.mod_ban_from_community.id.0),
ModlogCombinedView::ModFeaturePost(v) => ('K', v.mod_feature_post.id.0),
ModlogCombinedView::ModHideCommunity(v) => ('L', v.mod_hide_community.id.0),
ModlogCombinedView::ModLockPost(v) => ('M', v.mod_lock_post.id.0),
ModlogCombinedView::ModRemoveComment(v) => ('N', v.mod_remove_comment.id.0),
ModlogCombinedView::ModRemoveCommunity(v) => ('O', v.mod_remove_community.id.0),
ModlogCombinedView::ModRemovePost(v) => ('P', v.mod_remove_post.id.0),
ModlogCombinedView::ModTransferCommunity(v) => ('Q', v.mod_transfer_community.id.0),
};
// hex encoding to prevent ossification
ModlogCombinedPaginationCursor(format!("{prefix}-{id:x}"))
PaginationCursor::new(prefix, id)
}
pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
let mut query = modlog_combined::table
.select(ModlogCombined::as_select())
.select(Self::CursorData::as_select())
.into_boxed();
let (prefix, id_str) = self.0.split_once('-').ok_or_else(err_msg)?;
let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
query = match prefix {
"AdminAllowInstance" => query.filter(modlog_combined::admin_allow_instance_id.eq(id)),
"AdminBlockInstance" => query.filter(modlog_combined::admin_block_instance_id.eq(id)),
"AdminPurgeComment" => query.filter(modlog_combined::admin_purge_comment_id.eq(id)),
"AdminPurgeCommunity" => query.filter(modlog_combined::admin_purge_community_id.eq(id)),
"AdminPurgePerson" => query.filter(modlog_combined::admin_purge_person_id.eq(id)),
"AdminPurgePost" => query.filter(modlog_combined::admin_purge_post_id.eq(id)),
"ModAdd" => query.filter(modlog_combined::mod_add_id.eq(id)),
"ModAddCommunity" => query.filter(modlog_combined::mod_add_community_id.eq(id)),
"ModBan" => query.filter(modlog_combined::mod_ban_id.eq(id)),
"ModBanFromCommunity" => query.filter(modlog_combined::mod_ban_from_community_id.eq(id)),
"ModFeaturePost" => query.filter(modlog_combined::mod_feature_post_id.eq(id)),
"ModHideCommunity" => query.filter(modlog_combined::mod_hide_community_id.eq(id)),
"ModLockPost" => query.filter(modlog_combined::mod_lock_post_id.eq(id)),
"ModRemoveComment" => query.filter(modlog_combined::mod_remove_comment_id.eq(id)),
"ModRemoveCommunity" => query.filter(modlog_combined::mod_remove_community_id.eq(id)),
"ModRemovePost" => query.filter(modlog_combined::mod_remove_post_id.eq(id)),
"ModTransferCommunity" => query.filter(modlog_combined::mod_transfer_community_id.eq(id)),
_ => return Err(err_msg()),
'A' => query.filter(modlog_combined::admin_allow_instance_id.eq(id)),
'B' => query.filter(modlog_combined::admin_block_instance_id.eq(id)),
'C' => query.filter(modlog_combined::admin_purge_comment_id.eq(id)),
'D' => query.filter(modlog_combined::admin_purge_community_id.eq(id)),
'E' => query.filter(modlog_combined::admin_purge_person_id.eq(id)),
'F' => query.filter(modlog_combined::admin_purge_post_id.eq(id)),
'G' => query.filter(modlog_combined::mod_add_id.eq(id)),
'H' => query.filter(modlog_combined::mod_add_community_id.eq(id)),
'I' => query.filter(modlog_combined::mod_ban_id.eq(id)),
'J' => query.filter(modlog_combined::mod_ban_from_community_id.eq(id)),
'K' => query.filter(modlog_combined::mod_feature_post_id.eq(id)),
'L' => query.filter(modlog_combined::mod_hide_community_id.eq(id)),
'M' => query.filter(modlog_combined::mod_lock_post_id.eq(id)),
'N' => query.filter(modlog_combined::mod_remove_comment_id.eq(id)),
'O' => query.filter(modlog_combined::mod_remove_community_id.eq(id)),
'P' => query.filter(modlog_combined::mod_remove_post_id.eq(id)),
'Q' => query.filter(modlog_combined::mod_transfer_community_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(&mut get_conn(pool).await?).await?;
Ok(PaginationCursorData(token))
let token = query.first(conn).await?;
Ok(token)
}
}
#[derive(Clone)]
pub struct PaginationCursorData(ModlogCombined);
#[derive(Default)]
/// Querying / filtering the modlog.
pub struct ModlogCombinedQuery<'a> {
@ -315,7 +302,7 @@ pub struct ModlogCombinedQuery<'a> {
pub local_user: Option<&'a LocalUser>,
pub mod_person_id: Option<PersonId>,
pub other_person_id: Option<PersonId>,
pub page_after: Option<PaginationCursorData>,
pub cursor_data: Option<ModlogCombined>,
pub page_back: Option<bool>,
}
@ -392,12 +379,10 @@ impl ModlogCombinedQuery<'_> {
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
query = query

View file

@ -1,13 +1,11 @@
use crate::structs::{
CommentView,
LocalUserView,
PersonContentCombinedPaginationCursor,
PersonContentCombinedView,
PersonContentCombinedViewInternal,
PostView,
};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
@ -20,7 +18,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::PersonId,
newtypes::{PaginationCursor, PersonId},
schema::{
comment,
comment_actions,
@ -32,7 +30,6 @@ use lemmy_db_schema::{
person,
person_actions,
person_content_combined,
person_saved_combined,
post,
post_actions,
post_aggregates,
@ -40,11 +37,11 @@ use lemmy_db_schema::{
tag,
},
source::combined::person_content::{person_content_combined_keys as key, PersonContentCombined},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{functions::coalesce, get_conn, DbPool},
PersonContentType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
impl PersonContentCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
@ -140,142 +137,48 @@ impl PersonContentCombinedViewInternal {
.left_join(comment_actions_join)
.left_join(image_details_join)
}
#[diesel::dsl::auto_type(no_type_alias)]
pub(crate) fn joins_saved(my_person_id: PersonId) -> _ {
let item_creator = person::id;
let comment_join =
comment::table.on(person_saved_combined::comment_id.eq(comment::id.nullable()));
let post_join = post::table.on(
person_saved_combined::post_id
.eq(post::id.nullable())
.or(comment::post_id.eq(post::id)),
);
let item_creator_join = person::table.on(
comment::creator_id
.eq(item_creator)
// Need to filter out the post rows where the post_id given is null
// Otherwise you'll get duped post rows
.or(
post::creator_id
.eq(item_creator)
.and(person_saved_combined::post_id.is_not_null()),
),
);
let community_join = community::table.on(post::community_id.eq(community::id));
let creator_community_actions_join = creator_community_actions.on(
creator_community_actions
.field(community_actions::community_id)
.eq(post::community_id)
.and(
creator_community_actions
.field(community_actions::person_id)
.eq(item_creator),
),
);
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let community_actions_join = community_actions::table.on(
community_actions::community_id
.eq(post::community_id)
.and(community_actions::person_id.eq(my_person_id)),
);
let post_actions_join = post_actions::table.on(
post_actions::post_id
.eq(post::id)
.and(post_actions::person_id.eq(my_person_id)),
);
let person_actions_join = person_actions::table.on(
person_actions::target_id
.eq(item_creator)
.and(person_actions::person_id.eq(my_person_id)),
);
let comment_actions_join = comment_actions::table.on(
comment_actions::comment_id
.eq(comment::id)
.and(comment_actions::person_id.eq(my_person_id)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join = comment_aggregates::table
.on(person_saved_combined::comment_id.eq(comment_aggregates::comment_id.nullable()));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
person_saved_combined::table
.left_join(comment_join)
.inner_join(post_join)
.inner_join(item_creator_join)
.inner_join(community_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.inner_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(comment_actions_join)
.left_join(image_details_join)
}
}
impl PersonContentCombinedPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &PersonContentCombinedView) -> PersonContentCombinedPaginationCursor {
let (prefix, id) = match view {
impl PaginationCursorBuilder for PersonContentCombinedView {
type CursorData = PersonContentCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
PersonContentCombinedView::Comment(v) => ('C', v.comment.id.0),
PersonContentCombinedView::Post(v) => ('P', v.post.id.0),
};
// hex encoding to prevent ossification
PersonContentCombinedPaginationCursor(format!("{prefix}{id:x}"))
PaginationCursor::new(prefix, id)
}
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 = person_content_combined::table
.select(PersonContentCombined::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(person_content_combined::comment_id.eq(id)),
"P" => query.filter(person_content_combined::post_id.eq(id)),
_ => return Err(err_msg()),
};
let token = query.first(&mut get_conn(pool).await?).await?;
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
Ok(PaginationCursorData(token))
let mut query = person_content_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
query = match prefix {
'C' => query.filter(person_content_combined::comment_id.eq(id)),
'P' => query.filter(person_content_combined::post_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
#[derive(Clone)]
pub struct PaginationCursorData(PersonContentCombined);
#[derive(derive_new::new)]
pub struct PersonContentCombinedQuery {
pub creator_id: PersonId,
#[new(default)]
pub type_: Option<PersonContentType>,
#[new(default)]
pub page_after: Option<PaginationCursorData>,
pub cursor_data: Option<PersonContentCombined>,
#[new(default)]
pub page_back: Option<bool>,
}
@ -361,12 +264,10 @@ impl PersonContentCombinedQuery {
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
// Sorting by published

View file

@ -1,12 +1,14 @@
use crate::structs::{
CommentView,
LocalUserView,
PersonContentCombinedView,
PersonContentCombinedViewInternal,
PersonSavedCombinedPaginationCursor,
PersonSavedCombinedView,
PersonSavedCombinedViewInternal,
PostView,
};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
SelectableHelper,
@ -16,6 +18,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::{PaginationCursor, PersonId},
schema::{
comment,
comment_actions,
@ -34,57 +37,155 @@ use lemmy_db_schema::{
tag,
},
source::combined::person_saved::{person_saved_combined_keys as key, PersonSavedCombined},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{functions::coalesce, get_conn, DbPool},
PersonContentType,
};
use lemmy_utils::error::LemmyResult;
impl PersonSavedCombinedPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &PersonContentCombinedView) -> PersonSavedCombinedPaginationCursor {
let (prefix, id) = match view {
PersonContentCombinedView::Comment(v) => ('C', v.comment.id.0),
PersonContentCombinedView::Post(v) => ('P', v.post.id.0),
};
// hex encoding to prevent ossification
PersonSavedCombinedPaginationCursor(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 = person_saved_combined::table
.select(PersonSavedCombined::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(person_saved_combined::comment_id.eq(id)),
"P" => query.filter(person_saved_combined::post_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(PersonSavedCombined);
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[derive(Default)]
pub struct PersonSavedCombinedQuery {
pub type_: Option<PersonContentType>,
pub page_after: Option<PaginationCursorData>,
pub cursor_data: Option<PersonSavedCombined>,
pub page_back: Option<bool>,
}
impl PaginationCursorBuilder for PersonSavedCombinedView {
type CursorData = PersonSavedCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
PersonSavedCombinedView::Comment(v) => ('C', v.comment.id.0),
PersonSavedCombinedView::Post(v) => ('P', v.post.id.0),
};
PaginationCursor::new(prefix, id)
}
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
let mut query = person_saved_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
query = match prefix {
'C' => query.filter(person_saved_combined::comment_id.eq(id)),
'P' => query.filter(person_saved_combined::post_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
impl PersonSavedCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
pub(crate) fn joins(my_person_id: PersonId) -> _ {
let item_creator = person::id;
let comment_join =
comment::table.on(person_saved_combined::comment_id.eq(comment::id.nullable()));
let post_join = post::table.on(
person_saved_combined::post_id
.eq(post::id.nullable())
.or(comment::post_id.eq(post::id)),
);
let item_creator_join = person::table.on(
comment::creator_id
.eq(item_creator)
// Need to filter out the post rows where the post_id given is null
// Otherwise you'll get duped post rows
.or(
post::creator_id
.eq(item_creator)
.and(person_saved_combined::post_id.is_not_null()),
),
);
let community_join = community::table.on(post::community_id.eq(community::id));
let creator_community_actions_join = creator_community_actions.on(
creator_community_actions
.field(community_actions::community_id)
.eq(post::community_id)
.and(
creator_community_actions
.field(community_actions::person_id)
.eq(item_creator),
),
);
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let community_actions_join = community_actions::table.on(
community_actions::community_id
.eq(post::community_id)
.and(community_actions::person_id.eq(my_person_id)),
);
let post_actions_join = post_actions::table.on(
post_actions::post_id
.eq(post::id)
.and(post_actions::person_id.eq(my_person_id)),
);
let person_actions_join = person_actions::table.on(
person_actions::target_id
.eq(item_creator)
.and(person_actions::person_id.eq(my_person_id)),
);
let comment_actions_join = comment_actions::table.on(
comment_actions::comment_id
.eq(comment::id)
.and(comment_actions::person_id.eq(my_person_id)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join = comment_aggregates::table
.on(person_saved_combined::comment_id.eq(comment_aggregates::comment_id.nullable()));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
person_saved_combined::table
.left_join(comment_join)
.inner_join(post_join)
.inner_join(item_creator_join)
.inner_join(community_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.inner_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(comment_actions_join)
.left_join(image_details_join)
}
}
impl PersonSavedCombinedQuery {
pub async fn list(
self,
pool: &mut DbPool<'_>,
user: &LocalUserView,
) -> LemmyResult<Vec<PersonContentCombinedView>> {
) -> LemmyResult<Vec<PersonSavedCombinedView>> {
let my_person_id = user.local_user.person_id;
let conn = &mut get_conn(pool).await?;
@ -98,7 +199,7 @@ impl PersonSavedCombinedQuery {
.filter(tag::deleted.eq(false))
.single_value();
let mut query = PersonContentCombinedViewInternal::joins_saved(my_person_id)
let mut query = PersonSavedCombinedViewInternal::joins(my_person_id)
.filter(person_saved_combined::person_id.eq(my_person_id))
.select((
// Post-specific
@ -153,12 +254,10 @@ impl PersonSavedCombinedQuery {
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
// Sorting by saved desc
@ -167,9 +266,7 @@ impl PersonSavedCombinedQuery {
// Tie breaker
.then_desc(key::id);
let res = query
.load::<PersonContentCombinedViewInternal>(conn)
.await?;
let res = query.load::<PersonSavedCombinedViewInternal>(conn).await?;
// Map the query results to the enum
let out = res
@ -181,13 +278,62 @@ impl PersonSavedCombinedQuery {
}
}
impl InternalToCombinedView for PersonSavedCombinedViewInternal {
type CombinedView = PersonSavedCombinedView;
fn map_to_enum(self) -> Option<Self::CombinedView> {
// Use for a short alias
let v = self;
if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
Some(PersonSavedCombinedView::Comment(CommentView {
comment,
counts,
post: v.post,
community: v.community,
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,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else {
Some(PersonSavedCombinedView::Post(PostView {
post: v.post,
community: v.community,
unread_comments: v.post_unread_comments,
counts: v.post_counts,
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,
image_details: v.image_details,
banned_from_community: v.banned_from_community,
tags: v.post_tags,
can_mod: v.can_mod,
}))
}
}
}
#[cfg(test)]
#[expect(clippy::indexing_slicing)]
mod tests {
use crate::{
combined::person_saved_combined_view::PersonSavedCombinedQuery,
structs::{LocalUserView, PersonContentCombinedView},
structs::{LocalUserView, PersonSavedCombinedView},
};
use lemmy_db_schema::{
source::{
@ -309,19 +455,19 @@ mod tests {
assert_eq!(3, timmy_saved.len());
// Make sure the types and order are correct
if let PersonContentCombinedView::Post(v) = &timmy_saved[0] {
if let PersonSavedCombinedView::Post(v) = &timmy_saved[0] {
assert_eq!(data.timmy_post.id, v.post.id);
assert_eq!(data.timmy.id, v.post.creator_id);
} else {
panic!("wrong type");
}
if let PersonContentCombinedView::Comment(v) = &timmy_saved[1] {
if let PersonSavedCombinedView::Comment(v) = &timmy_saved[1] {
assert_eq!(data.sara_comment.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
} else {
panic!("wrong type");
}
if let PersonContentCombinedView::Comment(v) = &timmy_saved[2] {
if let PersonSavedCombinedView::Comment(v) = &timmy_saved[2] {
assert_eq!(data.sara_comment_2.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
} else {
@ -337,7 +483,7 @@ mod tests {
.await?;
assert_eq!(1, timmy_saved.len());
if let PersonContentCombinedView::Comment(v) = &timmy_saved[0] {
if let PersonSavedCombinedView::Comment(v) = &timmy_saved[0] {
assert_eq!(data.sara_comment_2.id, v.comment.id);
assert_eq!(data.sara.id, v.comment.creator_id);
} else {

View file

@ -4,7 +4,6 @@ use crate::structs::{
LocalUserView,
PostReportView,
PrivateMessageReportView,
ReportCombinedPaginationCursor,
ReportCombinedView,
ReportCombinedViewInternal,
};
@ -24,7 +23,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{self, creator_community_actions},
impls::community::community_follower_select_subscribed_type,
newtypes::{CommunityId, PersonId, PostId},
newtypes::{CommunityId, PaginationCursor, PersonId, PostId},
schema::{
comment,
comment_actions,
@ -46,11 +45,11 @@ use lemmy_db_schema::{
report_combined,
},
source::combined::report::{report_combined_keys as key, ReportCombined},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
ReportType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
impl ReportCombinedViewInternal {
#[diesel::dsl::auto_type(no_type_alias)]
@ -211,42 +210,43 @@ 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 {
impl PaginationCursorBuilder for ReportCombinedView {
type CursorData = ReportCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
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),
ReportCombinedView::Community(v) => ('Y', v.community_report.id.0),
};
// hex encoding to prevent ossification
ReportCombinedPaginationCursor(format!("{prefix}{id:x}"))
PaginationCursor::new(prefix, id)
}
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)),
"Y" => query.filter(report_combined::community_report_id.eq(id)),
_ => return Err(err_msg()),
};
let token = query.first(&mut get_conn(pool).await?).await?;
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
Ok(PaginationCursorData(token))
let mut query = report_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
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)),
'Y' => query.filter(report_combined::community_report_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
#[derive(Clone)]
pub struct PaginationCursorData(ReportCombined);
#[derive(Default)]
pub struct ReportCombinedQuery {
pub type_: Option<ReportType>,
@ -255,8 +255,8 @@ pub struct ReportCombinedQuery {
pub unresolved_only: Option<bool>,
/// For admins, also show reports with `violates_instance_rules=false`
pub show_community_rule_violations: Option<bool>,
pub cursor_data: Option<ReportCombined>,
pub my_reports_only: Option<bool>,
pub page_after: Option<PaginationCursorData>,
pub page_back: Option<bool>,
}
@ -342,12 +342,10 @@ impl ReportCombinedQuery {
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
if let Some(type_) = self.type_ {

View file

@ -4,13 +4,11 @@ use crate::structs::{
LocalUserView,
PersonView,
PostView,
SearchCombinedPaginationCursor,
SearchCombinedView,
SearchCombinedViewInternal,
};
use diesel::{
dsl::not,
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
@ -24,7 +22,7 @@ use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::{CommunityId, PersonId},
newtypes::{CommunityId, PaginationCursor, PersonId},
schema::{
comment,
comment_actions,
@ -45,7 +43,7 @@ use lemmy_db_schema::{
tag,
},
source::combined::search::{search_combined_keys as key, SearchCombined},
traits::InternalToCombinedView,
traits::{InternalToCombinedView, PaginationCursorBuilder},
utils::{
functions::coalesce,
fuzzy_search,
@ -59,7 +57,7 @@ use lemmy_db_schema::{
SearchSortType,
SearchType,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use SearchSortType::*;
impl SearchCombinedViewInternal {
@ -183,42 +181,43 @@ impl SearchCombinedViewInternal {
}
}
impl SearchCombinedPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &SearchCombinedView) -> SearchCombinedPaginationCursor {
let (prefix, id) = match view {
impl PaginationCursorBuilder for SearchCombinedView {
type CursorData = SearchCombined;
fn to_cursor(&self) -> PaginationCursor {
let (prefix, id) = match &self {
SearchCombinedView::Post(v) => ('P', v.post.id.0),
SearchCombinedView::Comment(v) => ('C', v.comment.id.0),
SearchCombinedView::Community(v) => ('O', v.community.id.0),
SearchCombinedView::Person(v) => ('E', v.person.id.0),
};
// hex encoding to prevent ossification
SearchCombinedPaginationCursor(format!("{prefix}{id:x}"))
PaginationCursor::new(prefix, id)
}
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 = search_combined::table
.select(SearchCombined::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 {
"P" => query.filter(search_combined::post_id.eq(id)),
"C" => query.filter(search_combined::comment_id.eq(id)),
"O" => query.filter(search_combined::community_id.eq(id)),
"E" => query.filter(search_combined::person_id.eq(id)),
_ => return Err(err_msg()),
};
let token = query.first(&mut get_conn(pool).await?).await?;
async fn from_cursor(
cursor: &PaginationCursor,
pool: &mut DbPool<'_>,
) -> LemmyResult<Self::CursorData> {
let conn = &mut get_conn(pool).await?;
let (prefix, id) = cursor.prefix_and_id()?;
Ok(PaginationCursorData(token))
let mut query = search_combined::table
.select(Self::CursorData::as_select())
.into_boxed();
query = match prefix {
'P' => query.filter(search_combined::post_id.eq(id)),
'C' => query.filter(search_combined::comment_id.eq(id)),
'O' => query.filter(search_combined::community_id.eq(id)),
'E' => query.filter(search_combined::person_id.eq(id)),
_ => return Err(LemmyErrorType::CouldntParsePaginationToken.into()),
};
let token = query.first(conn).await?;
Ok(token)
}
}
#[derive(Clone)]
pub struct PaginationCursorData(SearchCombined);
#[derive(Default)]
pub struct SearchCombinedQuery {
pub search_term: Option<String>,
@ -232,7 +231,7 @@ pub struct SearchCombinedQuery {
pub post_url_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub page_after: Option<PaginationCursorData>,
pub cursor_data: Option<SearchCombined>,
pub page_back: Option<bool>,
}
@ -399,14 +398,18 @@ impl SearchCombinedQuery {
}
}
// Filter by the time range
if let Some(time_range_seconds) = self.time_range_seconds {
query = query
.filter(search_combined::published.gt(now() - seconds_to_pg_interval(time_range_seconds)));
}
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();
query = query.before(self.cursor_data).limit_and_offset_from_end();
} else {
query = query.after(page_after);
query = query.after(self.cursor_data);
}
query = match self.sort.unwrap_or_default() {
@ -415,12 +418,6 @@ impl SearchCombinedQuery {
Top => query.then_desc(key::score),
};
// Filter by the time range
if let Some(time_range_seconds) = self.time_range_seconds {
query = query
.filter(search_combined::published.gt(now() - seconds_to_pg_interval(time_range_seconds)));
}
// finally use unique id as tie breaker
query = query.then_desc(key::id);

View file

@ -1,5 +1,5 @@
use crate::{
structs::{PaginationCursor, PostView},
structs::{PostPaginationCursor, PostView},
utils::filter_blocked,
};
use diesel::{
@ -229,11 +229,12 @@ impl PostView {
}
}
impl PaginationCursor {
// TODO This pagination cursor is a mess, get rid of it and have it match the others
impl PostPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &PostView) -> PaginationCursor {
pub fn after_post(view: &PostView) -> PostPaginationCursor {
// hex encoding to prevent ossification
PaginationCursor(format!("P{:x}", view.counts.post_id.0))
PostPaginationCursor(format!("P{:x}", view.counts.post_id.0))
}
pub async fn read(
&self,
@ -277,6 +278,7 @@ pub struct PostQuery<'a> {
// literal filter
pub community_id_just_for_prefetch: bool,
pub local_user: Option<&'a LocalUser>,
// TODO get rid of this
pub search_term: Option<String>,
pub url_only: Option<bool>,
pub read_only: Option<bool>,
@ -285,6 +287,7 @@ pub struct PostQuery<'a> {
pub title_only: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
// TODO these should be simple cursors like the others, not data
pub page_after: Option<PaginationCursorData>,
pub page_before_or_equal: Option<PostAggregates>,
pub page_back: Option<bool>,
@ -296,6 +299,7 @@ pub struct PostQuery<'a> {
}
impl<'a> PostQuery<'a> {
// TODO this should not be doing recursive fetching, get rid of it.
#[allow(clippy::expect_used)]
async fn prefetch_upper_bound_for_page_before(
&self,

View file

@ -303,28 +303,11 @@ pub struct PostReportView {
/// perspective. stringified since we might want to use arbitrary info later, with a P prepended to
/// prevent ossification (api users love to make assumptions (e.g. parse stuff that looks like
/// numbers as numbers) about apis that aren't part of the spec
// TODO this is a mess, get rid of it and prefer the one in db_schema
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[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))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ReportCombinedPaginationCursor(pub String);
/// like PaginationCursor but for the person_content_combined table
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct PersonContentCombinedPaginationCursor(pub String);
/// like PaginationCursor but for the person_saved_combined table
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct PersonSavedCombinedPaginationCursor(pub String);
pub struct PostPaginationCursor(pub String);
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@ -538,6 +521,48 @@ pub enum PersonContentCombinedView {
Comment(CommentView),
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
/// A combined person_saved view
pub(crate) struct PersonSavedCombinedViewInternal {
// Post-specific
pub post_counts: PostAggregates,
pub post_unread_comments: i64,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
pub post_hidden: bool,
pub my_post_vote: Option<i16>,
pub image_details: Option<ImageDetails>,
pub post_tags: PostTags,
// Comment-specific
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
// Shared
pub post: Post,
pub community: Community,
pub item_creator: Person,
pub subscribed: SubscribedType,
pub item_creator_is_admin: bool,
pub item_creator_is_moderator: bool,
pub item_creator_banned_from_community: bool,
pub item_creator_blocked: bool,
pub banned_from_community: bool,
pub can_mod: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
// Use serde's internal tagging, to work easier with javascript libraries
#[serde(tag = "type_")]
pub enum PersonSavedCombinedView {
Post(PostView),
Comment(CommentView),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -770,12 +795,6 @@ pub struct PrivateMessageView {
pub recipient: Person,
}
/// like PaginationCursor but for the report_combined table
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct InboxCombinedPaginationCursor(pub String);
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -1057,12 +1076,6 @@ pub struct AdminAllowInstanceView {
pub admin: Option<Person>,
}
/// like PaginationCursor but for the modlog_combined
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ModlogCombinedPaginationCursor(pub String);
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -1150,13 +1163,6 @@ pub enum ModlogCombinedView {
ModTransferCommunity(ModTransferCommunityView),
}
/// like PaginationCursor but for the modlog_combined
// TODO get rid of all these pagination cursors
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct SearchCombinedPaginationCursor(pub String);
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]

View file

@ -158,6 +158,7 @@ pub enum LemmyErrorType {
#[cfg_attr(feature = "full", ts(optional))]
error: Option<FederationError>,
},
CouldntParsePaginationToken,
}
/// Federation related errors, these dont need to be translated.