From 62e1790ae7adbab431c7baccd57857ae160ef2f0 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 23 Sep 2024 11:27:06 -0400 Subject: [PATCH] Adding saved_only, liked_only, and disliked_only filters to search. (#5034) * Adding saved_only, liked_only, and disliked_only filters to search. - Fixes #4547 * Removing duplicate Url return type for search (was actually post). - This now works like the post_title_only filter. * Address PR comments. * Add saved_only post_view test. --- crates/api_common/src/site.rs | 4 ++ crates/api_common/src/utils.rs | 12 ++++ crates/api_crud/src/post/read.rs | 3 +- crates/apub/src/api/list_posts.rs | 8 +-- crates/apub/src/api/search.rs | 109 ++++++++++++++++-------------- crates/db_schema/src/lib.rs | 1 - crates/db_views/src/post_view.rs | 72 +++++++++++++++----- 7 files changed, 134 insertions(+), 75 deletions(-) diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index a4f4ea71e..fc7ececc9 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -79,6 +79,10 @@ pub struct Search { pub page: Option, pub limit: Option, pub post_title_only: Option, + pub post_url_only: Option, + pub saved_only: Option, + pub liked_only: Option, + pub disliked_only: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index be70c2afe..db186247c 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -989,6 +989,18 @@ fn limit_expire_time(expires: DateTime) -> LemmyResult } } +#[tracing::instrument(skip_all)] +pub fn check_conflicting_like_filters( + liked_only: Option, + disliked_only: Option, +) -> LemmyResult<()> { + if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() { + Err(LemmyErrorType::ContradictingFilters)? + } else { + Ok(()) + } +} + pub async fn process_markdown( text: &str, slur_regex: &Option, diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index f597eecf4..7677d59ef 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -87,7 +87,8 @@ pub async fn get_post( // Fetch the cross_posts let cross_posts = if let Some(url) = &post_view.post.url { let mut x_posts = PostQuery { - url_search: Some(url.inner().as_str().into()), + url_only: Some(true), + search_term: Some(url.inner().as_str().into()), local_user: local_user.as_ref(), ..Default::default() } diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index 9b504dbe3..d75a82d3b 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -8,14 +8,14 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPosts, GetPostsResponse}, - utils::check_private_instance, + utils::{check_conflicting_like_filters, check_private_instance}, }; use lemmy_db_schema::source::community::Community; use lemmy_db_views::{ post_view::PostQuery, structs::{LocalUserView, PaginationCursor, SiteView}, }; -use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn list_posts( @@ -45,9 +45,7 @@ pub async fn list_posts( let liked_only = data.liked_only; let disliked_only = data.disliked_only; - if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() { - return Err(LemmyError::from(LemmyErrorType::ContradictingFilters)); - } + check_conflicting_like_filters(liked_only, disliked_only)?; let local_user = local_user_view.as_ref().map(|u| &u.local_user); let listing_type = Some(listing_type_with_default( diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index d50363d4a..492d2b087 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -4,7 +4,7 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, site::{Search, SearchResponse}, - utils::{check_private_instance, is_admin}, + utils::{check_conflicting_like_filters, check_private_instance, is_admin}, }; use lemmy_db_schema::{source::community::Community, utils::post_to_comment_sort_type, SearchType}; use lemmy_db_views::{ @@ -37,67 +37,87 @@ pub async fn search( // TODO no clean / non-nsfw searching rn - let q = data.q.clone(); - let page = data.page; - let limit = data.limit; - let sort = data.sort; - let listing_type = data.listing_type; - let search_type = data.type_.unwrap_or(SearchType::All); - let community_id = if let Some(name) = &data.community_name { + let Query(Search { + q, + community_id, + community_name, + creator_id, + type_, + sort, + listing_type, + page, + limit, + post_title_only, + post_url_only, + saved_only, + liked_only, + disliked_only, + }) = data; + + let q = q.clone(); + let search_type = type_.unwrap_or(SearchType::All); + let community_id = if let Some(name) = &community_name { Some( resolve_actor_identifier::(name, &context, &local_user_view, false) .await?, ) .map(|c| c.id) } else { - data.community_id + community_id }; - let creator_id = data.creator_id; let local_user = local_user_view.as_ref().map(|l| &l.local_user); - let post_title_only = data.post_title_only; + + check_conflicting_like_filters(liked_only, disliked_only)?; let posts_query = PostQuery { - sort: (sort), - listing_type: (listing_type), - community_id: (community_id), - creator_id: (creator_id), + sort, + listing_type, + community_id, + creator_id, local_user, - search_term: (Some(q.clone())), - page: (page), - limit: (limit), - title_only: (post_title_only), + search_term: Some(q.clone()), + page, + limit, + title_only: post_title_only, + url_only: post_url_only, + liked_only, + disliked_only, + saved_only, ..Default::default() }; let comment_query = CommentQuery { - sort: (sort.map(post_to_comment_sort_type)), - listing_type: (listing_type), - search_term: (Some(q.clone())), - community_id: (community_id), - creator_id: (creator_id), + sort: sort.map(post_to_comment_sort_type), + listing_type, + search_term: Some(q.clone()), + community_id, + creator_id, local_user, - page: (page), - limit: (limit), + page, + limit, + liked_only, + disliked_only, + saved_only, ..Default::default() }; let community_query = CommunityQuery { - sort: (sort), - listing_type: (listing_type), - search_term: (Some(q.clone())), + sort, + listing_type, + search_term: Some(q.clone()), local_user, - is_mod_or_admin: (is_admin), - page: (page), - limit: (limit), + is_mod_or_admin: is_admin, + page, + limit, ..Default::default() }; let person_query = PersonQuery { sort, - search_term: (Some(q.clone())), - listing_type: (listing_type), - page: (page), - limit: (limit), + search_term: Some(q.clone()), + listing_type, + page, + limit, }; match search_type { @@ -120,7 +140,7 @@ pub async fn search( SearchType::All => { // If the community or creator is included, dont search communities or users let community_or_creator_included = - data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); + community_id.is_some() || community_name.is_some() || creator_id.is_some(); posts = posts_query .list(&local_site.site, &mut context.pool()) @@ -142,21 +162,6 @@ pub async fn search( person_query.list(&mut context.pool()).await? }; } - SearchType::Url => { - posts = PostQuery { - sort: (sort), - listing_type: (listing_type), - community_id: (community_id), - creator_id: (creator_id), - url_search: (Some(q)), - local_user, - page: (page), - limit: (limit), - ..Default::default() - } - .list(&local_site.site, &mut context.pool()) - .await?; - } }; // Return the jwt diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 1e3d96a1d..7c57ddf28 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -182,7 +182,6 @@ pub enum SearchType { Posts, Communities, Users, - Url, } #[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)] diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 74202abda..5dee59538 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -382,22 +382,22 @@ fn queries<'a>() -> Queries< query = query.filter(community::hidden.eq(false)); } - if let Some(url_search) = &options.url_search { - query = query.filter(post::url.eq(url_search)); - } - if let Some(search_term) = &options.search_term { - let searcher = fuzzy_search(search_term); - query = if options.title_only.unwrap_or_default() { - query.filter(post::name.ilike(searcher)) + if options.url_only.unwrap_or_default() { + query = query.filter(post::url.eq(search_term)); } else { - query.filter( - post::name - .ilike(searcher.clone()) - .or(post::body.ilike(searcher)), - ) + let searcher = fuzzy_search(search_term); + query = if options.title_only.unwrap_or_default() { + query.filter(post::name.ilike(searcher)) + } else { + query.filter( + post::name + .ilike(searcher.clone()) + .or(post::body.ilike(searcher)), + ) + } + .filter(not(post::removed.or(post::deleted))); } - .filter(not(post::removed.or(post::deleted))); } if !options @@ -616,7 +616,7 @@ pub struct PostQuery<'a> { pub community_id_just_for_prefetch: bool, pub local_user: Option<&'a LocalUser>, pub search_term: Option, - pub url_search: Option, + pub url_only: Option, pub saved_only: Option, pub liked_only: Option, pub disliked_only: Option, @@ -765,10 +765,20 @@ mod tests { local_user_vote_display_mode::LocalUserVoteDisplayMode, person::{Person, PersonInsertForm}, person_block::{PersonBlock, PersonBlockForm}, - post::{Post, PostHide, PostInsertForm, PostLike, PostLikeForm, PostRead, PostUpdateForm}, + post::{ + Post, + PostHide, + PostInsertForm, + PostLike, + PostLikeForm, + PostRead, + PostSaved, + PostSavedForm, + PostUpdateForm, + }, site::Site, }, - traits::{Bannable, Blockable, Crud, Joinable, Likeable}, + traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable}, utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT}, CommunityVisibility, PostSortType, @@ -1215,6 +1225,36 @@ mod tests { cleanup(data, pool).await } + #[tokio::test] + #[serial] + async fn post_listing_saved_only() -> LemmyResult<()> { + let pool = &build_db_pool().await?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Save only the bot post + // The saved_only should only show the bot post + let post_save_form = PostSavedForm { + post_id: data.inserted_bot_post.id, + person_id: data.local_user_view.person.id, + }; + PostSaved::save(pool, &post_save_form).await?; + + // Read the saved only + let read_saved_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + saved_only: Some(true), + ..data.default_post_query() + } + .list(&data.site, pool) + .await?; + + // This should only include the bot post, not the one you created + assert_eq!(vec![POST_BY_BOT], names(&read_saved_post_listing)); + + cleanup(data, pool).await + } + #[tokio::test] #[serial] async fn creator_info() -> LemmyResult<()> {