From 389083418c2c16fdd54fe0f5d5d84d28f51c79de Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 18 Dec 2024 17:47:11 -0500 Subject: [PATCH] Filters done, working on tests. --- .../src/combined/search_combined_view.rs | 405 +++++++++++------- 1 file changed, 259 insertions(+), 146 deletions(-) diff --git a/crates/db_views/src/combined/search_combined_view.rs b/crates/db_views/src/combined/search_combined_view.rs index 576534f97..2a034a963 100644 --- a/crates/db_views/src/combined/search_combined_view.rs +++ b/crates/db_views/src/combined/search_combined_view.rs @@ -9,11 +9,13 @@ use crate::structs::{ SearchCombinedViewInternal, }; use diesel::{ + dsl::not, result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, + PgTextExpressionMethods, QueryDsl, SelectableHelper, }; @@ -21,7 +23,7 @@ use diesel_async::RunQueryDsl; use i_love_jesus::PaginatedQueryBuilder; use lemmy_db_schema::{ aliases::creator_community_actions, - newtypes::PersonId, + newtypes::{CommunityId, PersonId}, schema::{ comment, comment_actions, @@ -43,8 +45,10 @@ use lemmy_db_schema::{ combined::search::{search_combined_keys as key, SearchCombined}, community::CommunityFollower, }, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{actions, actions_alias, functions::coalesce, fuzzy_search, get_conn, DbPool}, InternalToCombinedView, + ListingType, + SearchType, }; use lemmy_utils::error::LemmyResult; @@ -84,12 +88,20 @@ impl SearchCombinedPaginationCursor { #[derive(Clone)] pub struct PaginationCursorData(SearchCombined); -#[derive(derive_new::new)] +#[derive(Default)] pub struct SearchCombinedQuery { - pub creator_id: PersonId, - #[new(default)] + pub search_term: Option, + pub community_id: Option, + pub creator_id: Option, + pub type_: Option, + // TODO sorts need to be added + // pub sort: Option, + pub listing_type: Option, + pub title_only: Option, + pub post_url_only: Option, + pub liked_only: Option, + pub disliked_only: Option, pub page_after: Option, - #[new(default)] pub page_back: Option, } @@ -132,7 +144,7 @@ impl SearchCombinedQuery { // For example, the creator must be the person table joined to either: // - post.creator_id // - comment.creator_id - let query = search_combined::table + let mut query = search_combined::table // The comment .left_join(comment::table.on(comment_join)) // The post @@ -217,6 +229,112 @@ impl SearchCombinedQuery { )) .into_boxed(); + // The filters + + // The search term + if let Some(search_term) = &self.search_term { + if self.post_url_only.unwrap_or_default() { + query = query.filter(post::url.eq(search_term)); + } else { + let searcher = fuzzy_search(search_term); + + let name_or_title_filter = post::name + .ilike(searcher.clone()) + .or(comment::content.ilike(searcher.clone())) + .or(community::name.ilike(searcher.clone())) + .or(community::title.ilike(searcher.clone())) + .or(person::name.ilike(searcher.clone())) + .or(person::display_name.ilike(searcher.clone())); + + let body_or_description_filter = post::body + .ilike(searcher.clone()) + .or(community::description.ilike(searcher.clone())); + + query = if self.title_only.unwrap_or_default() { + query.filter(name_or_title_filter) + } else { + query.filter(name_or_title_filter.or(body_or_description_filter)) + } + } + } + + // Community id + if let Some(community_id) = self.community_id { + query = query.filter(community::id.eq(community_id)); + } + + // Creator id + if let Some(creator_id) = self.creator_id { + query = query.filter(item_creator.eq(creator_id)); + } + + // Liked / disliked filter + if let Some(my_id) = my_person_id { + let not_creator_filter = item_creator.ne(my_id); + // TODO do I need not null checks for these? + let liked_disliked_filter = |score: i16| { + search_combined::post_id + .is_not_null() + .and(post_actions::like_score.eq(score)) + .or( + search_combined::comment_id + .is_not_null() + .and(comment_actions::like_score.eq(score)), + ) + }; + + if self.liked_only.unwrap_or_default() { + query = query + .filter(not_creator_filter) + .filter(liked_disliked_filter(1)); + } else if self.disliked_only.unwrap_or_default() { + query = query + .filter(not_creator_filter) + .filter(liked_disliked_filter(-1)); + } + }; + + // Type + if let Some(type_) = self.type_ { + query = match type_ { + SearchType::All => query, + SearchType::Posts => query.filter(search_combined::post_id.is_not_null()), + SearchType::Comments => query.filter(search_combined::comment_id.is_not_null()), + SearchType::Communities => query.filter(search_combined::community_id.is_not_null()), + SearchType::Users => query.filter(search_combined::person_id.is_not_null()), + } + } + + // Listing type + // TODO need null checks for this one? + let is_subscribed = community_actions::followed.is_not_null(); + match self.listing_type.unwrap_or_default() { + ListingType::Subscribed => query = query.filter(is_subscribed), + ListingType::Local => { + query = query + .filter(community::local.eq(true)) + .filter(community::hidden.eq(false).or(is_subscribed)); + } + ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::ModeratorView => { + query = query.filter(community_actions::became_moderator.is_not_null()); + } + } + + // Deleted / removed filters + // TODO need null checks for this? + query = query.filter(not( + comment::removed + .or(comment::deleted) + .or(post::removed) + .or(post::deleted) + .or(community::removed) + .or(community::deleted) + .or(community::removed) + .or(community::deleted) + .or(person::deleted), + )); + let mut query = PaginatedQueryBuilder::new(query); let page_after = self.page_after.map(|c| c.0); @@ -311,164 +429,159 @@ impl InternalToCombinedView for SearchCombinedViewInternal { } } -// #[cfg(test)] -// #[expect(clippy::indexing_slicing)] -// mod tests { +#[cfg(test)] +#[expect(clippy::indexing_slicing)] +mod tests { -// use crate::{ -// combined::search_combined_view::SearchCombinedQuery, -// structs::SearchCombinedView, -// }; -// use lemmy_db_schema::{ -// source::{ -// comment::{Comment, CommentInsertForm}, -// community::{Community, CommunityInsertForm}, -// instance::Instance, -// person::{Person, PersonInsertForm}, -// post::{Post, PostInsertForm}, -// }, -// traits::Crud, -// utils::{build_db_pool_for_tests, DbPool}, -// }; -// use lemmy_utils::error::LemmyResult; -// use pretty_assertions::assert_eq; -// use serial_test::serial; + use crate::{combined::search_combined_view::SearchCombinedQuery, structs::SearchCombinedView}; + use lemmy_db_schema::{ + assert_length, + source::{ + comment::{Comment, CommentInsertForm}, + community::{Community, CommunityInsertForm}, + instance::Instance, + person::{Person, PersonInsertForm}, + post::{Post, PostInsertForm}, + }, + traits::Crud, + utils::{build_db_pool_for_tests, DbPool}, + }; + use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; + use serial_test::serial; -// struct Data { -// instance: Instance, -// timmy: Person, -// sara: Person, -// timmy_post: Post, -// timmy_post_2: Post, -// sara_post: Post, -// timmy_comment: Comment, -// sara_comment: Comment, -// sara_comment_2: Comment, -// } + struct Data { + instance: Instance, + timmy: Person, + sara: Person, + community: Community, + community_2: Community, + timmy_post: Post, + timmy_post_2: Post, + sara_post: Post, + timmy_comment: Comment, + sara_comment: Comment, + sara_comment_2: Comment, + } -// async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { -// let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { + let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; -// let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv"); -// let timmy = Person::create(pool, &timmy_form).await?; + let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv"); + let timmy = Person::create(pool, &timmy_form).await?; -// let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv"); -// let sara = Person::create(pool, &sara_form).await?; + let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv"); + let sara = Person::create(pool, &sara_form).await?; -// let community_form = CommunityInsertForm::new( -// instance.id, -// "test community pcv".to_string(), -// "nada".to_owned(), -// "pubkey".to_string(), -// ); -// let community = Community::create(pool, &community_form).await?; + let community_form = CommunityInsertForm::new( + instance.id, + "asklemmy".to_string(), + "Ask Lemmy".to_owned(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; -// let timmy_post_form = PostInsertForm::new("timmy post prv".into(), timmy.id, community.id); -// let timmy_post = Post::create(pool, &timmy_post_form).await?; + let community_form_2 = CommunityInsertForm::new( + instance.id, + "startrek_ds9".to_string(), + "Star Trek - Deep Space Nine".to_owned(), + "pubkey".to_string(), + ); + let community_2 = Community::create(pool, &community_form_2).await?; -// let timmy_post_form_2 = PostInsertForm::new("timmy post prv 2".into(), timmy.id, -// community.id); let timmy_post_2 = Post::create(pool, &timmy_post_form_2).await?; + let timmy_post_form = PostInsertForm::new("timmy post prv".into(), timmy.id, community.id); + let timmy_post = Post::create(pool, &timmy_post_form).await?; -// let sara_post_form = PostInsertForm::new("sara post prv".into(), sara.id, community.id); -// let sara_post = Post::create(pool, &sara_post_form).await?; + let timmy_post_form_2 = PostInsertForm::new("timmy post prv 2".into(), timmy.id, community.id); + let timmy_post_2 = Post::create(pool, &timmy_post_form_2).await?; -// let timmy_comment_form = -// CommentInsertForm::new(timmy.id, timmy_post.id, "timmy comment prv".into()); -// let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?; + let sara_post_form = PostInsertForm::new("sara post prv".into(), sara.id, community.id); + let sara_post = Post::create(pool, &sara_post_form).await?; -// let sara_comment_form = -// CommentInsertForm::new(sara.id, timmy_post.id, "sara comment prv".into()); -// let sara_comment = Comment::create(pool, &sara_comment_form, None).await?; + let timmy_comment_form = + CommentInsertForm::new(timmy.id, timmy_post.id, "timmy comment prv".into()); + let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?; -// let sara_comment_form_2 = -// CommentInsertForm::new(sara.id, timmy_post_2.id, "sara comment prv 2".into()); -// let sara_comment_2 = Comment::create(pool, &sara_comment_form_2, None).await?; + let sara_comment_form = + CommentInsertForm::new(sara.id, timmy_post.id, "sara comment prv".into()); + let sara_comment = Comment::create(pool, &sara_comment_form, None).await?; -// Ok(Data { -// instance, -// timmy, -// sara, -// timmy_post, -// timmy_post_2, -// sara_post, -// timmy_comment, -// sara_comment, -// sara_comment_2, -// }) -// } + let sara_comment_form_2 = + CommentInsertForm::new(sara.id, timmy_post_2.id, "sara comment prv 2".into()); + let sara_comment_2 = Comment::create(pool, &sara_comment_form_2, None).await?; -// async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> { -// Instance::delete(pool, data.instance.id).await?; + Ok(Data { + instance, + timmy, + sara, + community, + community_2, + timmy_post, + timmy_post_2, + sara_post, + timmy_comment, + sara_comment, + sara_comment_2, + }) + } -// Ok(()) -// } + async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> { + Instance::delete(pool, data.instance.id).await?; -// #[tokio::test] -// #[serial] -// async fn test_combined() -> LemmyResult<()> { -// let pool = &build_db_pool_for_tests(); -// let pool = &mut pool.into(); -// let data = init_data(pool).await?; + Ok(()) + } -// // Do a batch read of timmy -// let timmy_content = SearchCombinedQuery::new(data.timmy.id) -// .list(pool, &None) -// .await?; -// assert_eq!(3, timmy_content.len()); + #[tokio::test] + #[serial] + async fn community_search() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests(); + let pool = &mut pool.into(); + let data = init_data(pool).await?; -// // Make sure the types are correct -// if let SearchCombinedView::Comment(v) = &timmy_content[0] { -// assert_eq!(data.timmy_comment.id, v.comment.id); -// assert_eq!(data.timmy.id, v.creator.id); -// } else { -// panic!("wrong type"); -// } -// if let SearchCombinedView::Post(v) = &timmy_content[1] { -// assert_eq!(data.timmy_post_2.id, v.post.id); -// assert_eq!(data.timmy.id, v.post.creator_id); -// } else { -// panic!("wrong type"); -// } -// if let SearchCombinedView::Post(v) = &timmy_content[2] { -// assert_eq!(data.timmy_post.id, v.post.id); -// assert_eq!(data.timmy.id, v.post.creator_id); -// } else { -// panic!("wrong type"); -// } + // Community search + let community_search = SearchCombinedQuery::default().list(pool, &None).await?; + assert_length!(2, community_search); -// // Do a batch read of sara -// let sara_content = SearchCombinedQuery::new(data.sara.id) -// .list(pool, &None) -// .await?; -// assert_eq!(3, sara_content.len()); + // Make sure the types are correct + if let SearchCombinedView::Community(v) = &community_search[0] { + assert_eq!(data.community_2.id, v.community.id); + } else { + panic!("wrong type"); + } -// // Make sure the report types are correct -// if let SearchCombinedView::Comment(v) = &sara_content[0] { -// assert_eq!(data.sara_comment_2.id, v.comment.id); -// assert_eq!(data.sara.id, v.creator.id); -// // This one was to timmy_post_2 -// assert_eq!(data.timmy_post_2.id, v.post.id); -// assert_eq!(data.timmy.id, v.post.creator_id); -// } else { -// panic!("wrong type"); -// } -// if let SearchCombinedView::Comment(v) = &sara_content[1] { -// assert_eq!(data.sara_comment.id, v.comment.id); -// assert_eq!(data.sara.id, v.creator.id); -// assert_eq!(data.timmy_post.id, v.post.id); -// assert_eq!(data.timmy.id, v.post.creator_id); -// } else { -// panic!("wrong type"); -// } -// if let SearchCombinedView::Post(v) = &sara_content[2] { -// assert_eq!(data.sara_post.id, v.post.id); -// assert_eq!(data.sara.id, v.post.creator_id); -// } else { -// panic!("wrong type"); -// } + if let SearchCombinedView::Community(v) = &community_search[1] { + assert_eq!(data.community.id, v.community.id); + } else { + panic!("wrong type"); + } -// cleanup(data, pool).await?; + // Filtered by id + let community_search_by_id = SearchCombinedQuery { + community_id: Some(data.community.id), + ..Default::default() + } + .list(pool, &None) + .await?; + assert_length!(1, community_search_by_id); -// Ok(()) -// } -// } + // Using a term + let community_search_by_name = SearchCombinedQuery { + search_term: Some("Ask".into()), + ..Default::default() + } + .list(pool, &None) + .await?; + + assert_length!(1, community_search_by_name); + if let SearchCombinedView::Community(v) = &community_search_by_name[0] { + // The asklemmy community + assert_eq!(data.community.id, v.community.id); + } else { + panic!("wrong type"); + } + + cleanup(data, pool).await?; + + Ok(()) + } +}