mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-03-13 15:02:44 +00:00
Adding pagination for GetBannedPersons (#5428)
* Extracting pagination cursor utils into a trait. - Fixes #5275 * Refactoring to avoid stack overflows. * Fixing api_common feature. * Adding pagination for GetBannedPersons. - Must come after #5424 - Fixes #2847 * Rename the traits and paginationcursor::new * Using combined trait. * Removing empty files. * Merge from main, limit fetch. * Adding local ban check, and ignore_page_limits. * Only do page limits if not admin.
This commit is contained in:
parent
304df35f3b
commit
91d10092b6
8 changed files with 182 additions and 40 deletions
|
@ -11,7 +11,7 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PersonView};
|
||||
use lemmy_db_views::{person::person_view::PersonQuery, structs::LocalUserView};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
pub async fn add_admin(
|
||||
|
@ -57,7 +57,12 @@ pub async fn add_admin(
|
|||
|
||||
ModAdd::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
let admins = PersonQuery {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(Json(AddAdminResponse { admins }))
|
||||
}
|
||||
|
|
|
@ -1,16 +1,40 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
|
||||
use lemmy_db_views::structs::{LocalUserView, PersonView};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{BannedPersonsResponse, ListBannedPersons},
|
||||
utils::is_admin,
|
||||
};
|
||||
use lemmy_db_schema::traits::PaginationCursorBuilder;
|
||||
use lemmy_db_views::{
|
||||
person::person_view::PersonQuery,
|
||||
structs::{LocalUserView, PersonView},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn list_banned_users(
|
||||
data: Json<ListBannedPersons>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<BannedPersonsResponse>> {
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let banned = PersonView::banned(&mut context.pool()).await?;
|
||||
let cursor_data = if let Some(cursor) = &data.page_cursor {
|
||||
Some(PersonView::from_cursor(cursor, &mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Json(BannedPersonsResponse { banned }))
|
||||
let banned = PersonQuery {
|
||||
banned_only: Some(true),
|
||||
cursor_data,
|
||||
limit: data.limit,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let next_page = banned.last().map(PaginationCursorBuilder::to_cursor);
|
||||
|
||||
Ok(Json(BannedPersonsResponse { banned, next_page }))
|
||||
}
|
||||
|
|
|
@ -12,7 +12,10 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PersonView, SiteView};
|
||||
use lemmy_db_views::{
|
||||
person::person_view::PersonQuery,
|
||||
structs::{LocalUserView, SiteView},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
VERSION,
|
||||
|
@ -25,7 +28,12 @@ pub async fn leave_admin(
|
|||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
let admins = PersonQuery {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
if admins.len() == 1 {
|
||||
Err(LemmyErrorType::CannotLeaveAdmin)?
|
||||
}
|
||||
|
@ -55,7 +63,12 @@ pub async fn leave_admin(
|
|||
|
||||
// Reread site and admins
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
let admins = PersonQuery {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
|
|
|
@ -344,13 +344,29 @@ pub struct BanPerson {
|
|||
pub expires: Option<i64>,
|
||||
}
|
||||
|
||||
// TODO, this should be paged, since the list can be quite long.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// List the banned persons.
|
||||
pub struct ListBannedPersons {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_cursor: Option<PaginationCursor>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page_back: Option<bool>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The list of banned persons.
|
||||
pub struct BannedPersonsResponse {
|
||||
pub banned: Vec<PersonView>,
|
||||
/// 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)]
|
||||
|
|
|
@ -8,7 +8,10 @@ use lemmy_db_schema::source::{
|
|||
oauth_provider::OAuthProvider,
|
||||
tagline::Tagline,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PersonView, SiteView};
|
||||
use lemmy_db_views::{
|
||||
person::person_view::PersonQuery,
|
||||
structs::{LocalUserView, SiteView},
|
||||
};
|
||||
use lemmy_utils::{build_cache, error::LemmyResult, CacheLock, VERSION};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
|
@ -47,7 +50,12 @@ pub async fn get_site_v4(
|
|||
|
||||
async fn read_site(context: &LemmyContext) -> LemmyResult<GetSiteResponse> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
let admins = PersonQuery {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||
|
|
|
@ -8,6 +8,8 @@ use crate::{
|
|||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use diesel::{dsl, expression_methods::NullableExpressionMethods};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -15,10 +17,14 @@ use ts_rs::TS;
|
|||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Identifiable, TS, CursorKeysModule)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person))]
|
||||
#[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 = person_keys))]
|
||||
/// A person.
|
||||
pub struct Person {
|
||||
pub id: PersonId,
|
||||
|
|
|
@ -268,7 +268,13 @@ pub fn limit_and_offset(
|
|||
}
|
||||
None => 1,
|
||||
};
|
||||
let limit = match limit {
|
||||
let limit = limit_fetch(limit)?;
|
||||
let offset = limit * (page - 1);
|
||||
Ok((limit, offset))
|
||||
}
|
||||
|
||||
pub fn limit_fetch(limit: Option<i64>) -> Result<i64, diesel::result::Error> {
|
||||
Ok(match limit {
|
||||
Some(limit) => {
|
||||
if !(1..=FETCH_LIMIT_MAX).contains(&limit) {
|
||||
return Err(QueryBuilderError(
|
||||
|
@ -278,9 +284,7 @@ pub fn limit_and_offset(
|
|||
limit
|
||||
}
|
||||
None => FETCH_LIMIT_DEFAULT,
|
||||
};
|
||||
let offset = limit * (page - 1);
|
||||
Ok((limit, offset))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn limit_and_offset_unlimited(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
|
||||
|
|
|
@ -8,11 +8,39 @@ use diesel::{
|
|||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::PaginatedQueryBuilder;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
newtypes::{PaginationCursor, PersonId},
|
||||
schema::{local_user, person},
|
||||
utils::{get_conn, now, DbPool},
|
||||
source::person::{person_keys as key, Person},
|
||||
traits::PaginationCursorBuilder,
|
||||
utils::{get_conn, limit_fetch, now, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
impl PaginationCursorBuilder for PersonView {
|
||||
type CursorData = Person;
|
||||
|
||||
fn to_cursor(&self) -> PaginationCursor {
|
||||
PaginationCursor::new('P', self.person.id.0)
|
||||
}
|
||||
|
||||
async fn from_cursor(
|
||||
cursor: &PaginationCursor,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Self::CursorData> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let id = cursor.prefix_and_id()?.1;
|
||||
|
||||
let token = person::table
|
||||
.select(Self::CursorData::as_select())
|
||||
.filter(person::id.eq(id))
|
||||
.first(conn)
|
||||
.await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonView {
|
||||
#[diesel::dsl::auto_type(no_type_alias)]
|
||||
|
@ -37,33 +65,61 @@ impl PersonView {
|
|||
|
||||
query.first(conn).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn admins(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
Self::joins()
|
||||
.filter(person::deleted.eq(false))
|
||||
.filter(local_user::admin.eq(true))
|
||||
.order_by(person::published)
|
||||
.select(Self::as_select())
|
||||
.load::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct PersonQuery {
|
||||
pub admins_only: Option<bool>,
|
||||
pub banned_only: Option<bool>,
|
||||
pub cursor_data: Option<Person>,
|
||||
pub page_back: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
pub async fn banned(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||
impl PersonQuery {
|
||||
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonView>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
Self::joins()
|
||||
|
||||
let mut query = PersonView::joins()
|
||||
.filter(person::deleted.eq(false))
|
||||
.filter(
|
||||
person::banned.eq(true).and(
|
||||
.select(PersonView::as_select())
|
||||
.into_boxed();
|
||||
|
||||
// Filters
|
||||
if self.banned_only.unwrap_or_default() {
|
||||
query = query.filter(
|
||||
person::local.and(person::banned).and(
|
||||
person::ban_expires
|
||||
.is_null()
|
||||
.or(person::ban_expires.gt(now().nullable())),
|
||||
),
|
||||
)
|
||||
.order_by(person::published)
|
||||
.select(Self::as_select())
|
||||
.load::<Self>(conn)
|
||||
.await
|
||||
);
|
||||
}
|
||||
|
||||
if self.admins_only.unwrap_or_default() {
|
||||
query = query.filter(local_user::admin);
|
||||
} else {
|
||||
// Only use page limits if its not an admin fetch
|
||||
let limit = limit_fetch(self.limit)?;
|
||||
query = query.limit(limit);
|
||||
}
|
||||
|
||||
let mut query = PaginatedQueryBuilder::new(query);
|
||||
|
||||
if self.page_back.unwrap_or_default() {
|
||||
query = query.before(self.cursor_data).limit_and_offset_from_end();
|
||||
} else {
|
||||
query = query.after(self.cursor_data);
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
query = query
|
||||
.then_desc(key::published)
|
||||
// Tie breaker
|
||||
.then_desc(key::id);
|
||||
|
||||
let res = query.load::<PersonView>(conn).await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +230,12 @@ mod tests {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let list = PersonView::banned(pool).await?;
|
||||
let list = PersonQuery {
|
||||
banned_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await?;
|
||||
assert_length!(1, list);
|
||||
assert_eq!(list[0].person.id, data.alice.id);
|
||||
|
||||
|
@ -198,7 +259,12 @@ mod tests {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let list = PersonView::admins(pool).await?;
|
||||
let list = PersonQuery {
|
||||
admins_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await?;
|
||||
assert_length!(1, list);
|
||||
assert_eq!(list[0].person.id, data.alice.id);
|
||||
|
||||
|
|
Loading…
Reference in a new issue