mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-22 04:23:07 +00:00
Add community alphabetic sorting (#5056)
* Started * Finished? Need to write tests * Formatting * Formatting * Formatting * Write tests * Formatting * Formatting * Formatting * Unnecessary lifetime * Safety * Unwrap * Formatting * Formatting * Fix local_only test * Formatting * Name consistency * Adding lower to community name sort. --------- Co-authored-by: Dessalines <tyhou13@gmx.com> Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
9509ef1706
commit
859dfb3f81
4 changed files with 162 additions and 25 deletions
|
@ -3,9 +3,13 @@ use lemmy_db_schema::{
|
||||||
source::site::Site,
|
source::site::Site,
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
ListingType,
|
ListingType,
|
||||||
PostSortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
|
use lemmy_db_views_actor::structs::{
|
||||||
|
CommunityModeratorView,
|
||||||
|
CommunitySortType,
|
||||||
|
CommunityView,
|
||||||
|
PersonView,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
@ -74,7 +78,7 @@ pub struct CommunityResponse {
|
||||||
/// Fetches a list of communities.
|
/// Fetches a list of communities.
|
||||||
pub struct ListCommunities {
|
pub struct ListCommunities {
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<PostSortType>,
|
pub sort: Option<CommunitySortType>,
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
|
|
@ -12,7 +12,11 @@ use lemmy_db_views::{
|
||||||
post_view::PostQuery,
|
post_view::PostQuery,
|
||||||
structs::{LocalUserView, SiteView},
|
structs::{LocalUserView, SiteView},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery};
|
use lemmy_db_views_actor::{
|
||||||
|
community_view::CommunityQuery,
|
||||||
|
person_view::PersonQuery,
|
||||||
|
structs::CommunitySortType,
|
||||||
|
};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -102,7 +106,7 @@ pub async fn search(
|
||||||
};
|
};
|
||||||
|
|
||||||
let community_query = CommunityQuery {
|
let community_query = CommunityQuery {
|
||||||
sort,
|
sort: sort.map(CommunitySortType::from),
|
||||||
listing_type,
|
listing_type,
|
||||||
search_term: Some(q.clone()),
|
search_term: Some(q.clone()),
|
||||||
title_only,
|
title_only,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::structs::{CommunityModeratorView, CommunityView, PersonView};
|
use crate::structs::{CommunityModeratorView, CommunitySortType, CommunityView, PersonView};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
pg::Pg,
|
pg::Pg,
|
||||||
result::Error,
|
result::Error,
|
||||||
|
@ -22,7 +22,16 @@ use lemmy_db_schema::{
|
||||||
instance_block,
|
instance_block,
|
||||||
},
|
},
|
||||||
source::{community::CommunityFollower, local_user::LocalUser, site::Site},
|
source::{community::CommunityFollower, local_user::LocalUser, site::Site},
|
||||||
utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
|
utils::{
|
||||||
|
functions::lower,
|
||||||
|
fuzzy_search,
|
||||||
|
limit_and_offset,
|
||||||
|
DbConn,
|
||||||
|
DbPool,
|
||||||
|
ListFn,
|
||||||
|
Queries,
|
||||||
|
ReadFn,
|
||||||
|
},
|
||||||
ListingType,
|
ListingType,
|
||||||
PostSortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
|
@ -103,7 +112,7 @@ fn queries<'a>() -> Queries<
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
|
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
|
||||||
use PostSortType::*;
|
use CommunitySortType::*;
|
||||||
|
|
||||||
// The left join below will return None in this case
|
// The left join below will return None in this case
|
||||||
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
|
||||||
|
@ -148,6 +157,8 @@ fn queries<'a>() -> Queries<
|
||||||
}
|
}
|
||||||
TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
|
TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
|
||||||
TopWeek => query = query.order_by(community_aggregates::users_active_week.desc()),
|
TopWeek => query = query.order_by(community_aggregates::users_active_week.desc()),
|
||||||
|
NameAsc => query = query.order_by(lower(community::name).asc()),
|
||||||
|
NameDesc => query = query.order_by(lower(community::name).desc()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(listing_type) = options.listing_type {
|
if let Some(listing_type) = options.listing_type {
|
||||||
|
@ -228,10 +239,36 @@ impl CommunityView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PostSortType> for CommunitySortType {
|
||||||
|
fn from(value: PostSortType) -> Self {
|
||||||
|
match value {
|
||||||
|
PostSortType::Active => Self::Active,
|
||||||
|
PostSortType::Hot => Self::Hot,
|
||||||
|
PostSortType::New => Self::New,
|
||||||
|
PostSortType::Old => Self::Old,
|
||||||
|
PostSortType::TopDay => Self::TopDay,
|
||||||
|
PostSortType::TopWeek => Self::TopWeek,
|
||||||
|
PostSortType::TopMonth => Self::TopMonth,
|
||||||
|
PostSortType::TopYear => Self::TopYear,
|
||||||
|
PostSortType::TopAll => Self::TopAll,
|
||||||
|
PostSortType::MostComments => Self::MostComments,
|
||||||
|
PostSortType::NewComments => Self::NewComments,
|
||||||
|
PostSortType::TopHour => Self::TopHour,
|
||||||
|
PostSortType::TopSixHour => Self::TopSixHour,
|
||||||
|
PostSortType::TopTwelveHour => Self::TopTwelveHour,
|
||||||
|
PostSortType::TopThreeMonths => Self::TopThreeMonths,
|
||||||
|
PostSortType::TopSixMonths => Self::TopSixMonths,
|
||||||
|
PostSortType::TopNineMonths => Self::TopNineMonths,
|
||||||
|
PostSortType::Controversial => Self::Controversial,
|
||||||
|
PostSortType::Scaled => Self::Scaled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CommunityQuery<'a> {
|
pub struct CommunityQuery<'a> {
|
||||||
pub listing_type: Option<ListingType>,
|
pub listing_type: Option<ListingType>,
|
||||||
pub sort: Option<PostSortType>,
|
pub sort: Option<CommunitySortType>,
|
||||||
pub local_user: Option<&'a LocalUser>,
|
pub local_user: Option<&'a LocalUser>,
|
||||||
pub search_term: Option<String>,
|
pub search_term: Option<String>,
|
||||||
pub title_only: Option<bool>,
|
pub title_only: Option<bool>,
|
||||||
|
@ -250,7 +287,10 @@ impl<'a> CommunityQuery<'a> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{community_view::CommunityQuery, structs::CommunityView};
|
use crate::{
|
||||||
|
community_view::CommunityQuery,
|
||||||
|
structs::{CommunitySortType, CommunityView},
|
||||||
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityInsertForm, CommunityUpdateForm},
|
community::{Community, CommunityInsertForm, CommunityUpdateForm},
|
||||||
|
@ -270,7 +310,7 @@ mod tests {
|
||||||
struct Data {
|
struct Data {
|
||||||
inserted_instance: Instance,
|
inserted_instance: Instance,
|
||||||
local_user: LocalUser,
|
local_user: LocalUser,
|
||||||
inserted_community: Community,
|
inserted_communities: [Community; 3],
|
||||||
site: Site,
|
site: Site,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,13 +326,38 @@ mod tests {
|
||||||
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
||||||
let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?;
|
let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?;
|
||||||
|
|
||||||
let new_community = CommunityInsertForm::new(
|
let inserted_communities = [
|
||||||
inserted_instance.id,
|
Community::create(
|
||||||
"test_community_3".to_string(),
|
pool,
|
||||||
"nada".to_owned(),
|
&CommunityInsertForm::new(
|
||||||
"pubkey".to_string(),
|
inserted_instance.id,
|
||||||
);
|
"test_community_1".to_string(),
|
||||||
let inserted_community = Community::create(pool, &new_community).await?;
|
"nada1".to_owned(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
Community::create(
|
||||||
|
pool,
|
||||||
|
&CommunityInsertForm::new(
|
||||||
|
inserted_instance.id,
|
||||||
|
"test_community_2".to_string(),
|
||||||
|
"nada2".to_owned(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
Community::create(
|
||||||
|
pool,
|
||||||
|
&CommunityInsertForm::new(
|
||||||
|
inserted_instance.id,
|
||||||
|
"test_community_3".to_string(),
|
||||||
|
"nada3".to_owned(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
];
|
||||||
|
|
||||||
let url = Url::parse("http://example.com")?;
|
let url = Url::parse("http://example.com")?;
|
||||||
let site = Site {
|
let site = Site {
|
||||||
|
@ -316,13 +381,15 @@ mod tests {
|
||||||
Ok(Data {
|
Ok(Data {
|
||||||
inserted_instance,
|
inserted_instance,
|
||||||
local_user,
|
local_user,
|
||||||
inserted_community,
|
inserted_communities,
|
||||||
site,
|
site,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||||
Community::delete(pool, data.inserted_community.id).await?;
|
for Community { id, .. } in data.inserted_communities {
|
||||||
|
Community::delete(pool, id).await?;
|
||||||
|
}
|
||||||
Person::delete(pool, data.local_user.person_id).await?;
|
Person::delete(pool, data.local_user.person_id).await?;
|
||||||
Instance::delete(pool, data.inserted_instance.id).await?;
|
Instance::delete(pool, data.inserted_instance.id).await?;
|
||||||
|
|
||||||
|
@ -338,7 +405,7 @@ mod tests {
|
||||||
|
|
||||||
Community::update(
|
Community::update(
|
||||||
pool,
|
pool,
|
||||||
data.inserted_community.id,
|
data.inserted_communities[0].id,
|
||||||
&CommunityUpdateForm {
|
&CommunityUpdateForm {
|
||||||
visibility: Some(CommunityVisibility::LocalOnly),
|
visibility: Some(CommunityVisibility::LocalOnly),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -351,7 +418,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(0, unauthenticated_query.len());
|
assert_eq!(
|
||||||
|
data.inserted_communities.len() - 1,
|
||||||
|
unauthenticated_query.len()
|
||||||
|
);
|
||||||
|
|
||||||
let authenticated_query = CommunityQuery {
|
let authenticated_query = CommunityQuery {
|
||||||
local_user: Some(&data.local_user),
|
local_user: Some(&data.local_user),
|
||||||
|
@ -359,15 +429,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(1, authenticated_query.len());
|
assert_eq!(data.inserted_communities.len(), authenticated_query.len());
|
||||||
|
|
||||||
let unauthenticated_community =
|
let unauthenticated_community =
|
||||||
CommunityView::read(pool, data.inserted_community.id, None, false).await;
|
CommunityView::read(pool, data.inserted_communities[0].id, None, false).await;
|
||||||
assert!(unauthenticated_community.is_err());
|
assert!(unauthenticated_community.is_err());
|
||||||
|
|
||||||
let authenticated_community = CommunityView::read(
|
let authenticated_community = CommunityView::read(
|
||||||
pool,
|
pool,
|
||||||
data.inserted_community.id,
|
data.inserted_communities[0].id,
|
||||||
Some(&data.local_user),
|
Some(&data.local_user),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
@ -376,4 +446,34 @@ mod tests {
|
||||||
|
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn community_sort_name() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let data = init_data(pool).await?;
|
||||||
|
|
||||||
|
let query = CommunityQuery {
|
||||||
|
sort: Some(CommunitySortType::NameAsc),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let communities = query.list(&data.site, pool).await?;
|
||||||
|
for (i, c) in communities.iter().enumerate().skip(1) {
|
||||||
|
let prev = communities.get(i - 1).expect("No previous community?");
|
||||||
|
assert!(c.community.title.cmp(&prev.community.title).is_ge());
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = CommunityQuery {
|
||||||
|
sort: Some(CommunitySortType::NameDesc),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let communities = query.list(&data.site, pool).await?;
|
||||||
|
for (i, c) in communities.iter().enumerate().skip(1) {
|
||||||
|
let prev = communities.get(i - 1).expect("No previous community?");
|
||||||
|
assert!(c.community.title.cmp(&prev.community.title).is_le());
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup(data, pool).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,35 @@ pub struct CommunityView {
|
||||||
pub banned_from_community: bool,
|
pub banned_from_community: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The community sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub enum CommunitySortType {
|
||||||
|
#[default]
|
||||||
|
Active,
|
||||||
|
Hot,
|
||||||
|
New,
|
||||||
|
Old,
|
||||||
|
TopDay,
|
||||||
|
TopWeek,
|
||||||
|
TopMonth,
|
||||||
|
TopYear,
|
||||||
|
TopAll,
|
||||||
|
MostComments,
|
||||||
|
NewComments,
|
||||||
|
TopHour,
|
||||||
|
TopSixHour,
|
||||||
|
TopTwelveHour,
|
||||||
|
TopThreeMonths,
|
||||||
|
TopSixMonths,
|
||||||
|
TopNineMonths,
|
||||||
|
Controversial,
|
||||||
|
Scaled,
|
||||||
|
NameAsc,
|
||||||
|
NameDesc,
|
||||||
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||||
|
|
Loading…
Reference in a new issue