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.
This commit is contained in:
Dessalines 2024-09-23 11:27:06 -04:00 committed by GitHub
parent a8843335a6
commit 62e1790ae7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 134 additions and 75 deletions

View file

@ -79,6 +79,10 @@ pub struct Search {
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
pub post_title_only: Option<bool>, pub post_title_only: Option<bool>,
pub post_url_only: Option<bool>,
pub saved_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View file

@ -989,6 +989,18 @@ fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>
} }
} }
#[tracing::instrument(skip_all)]
pub fn check_conflicting_like_filters(
liked_only: Option<bool>,
disliked_only: Option<bool>,
) -> LemmyResult<()> {
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
Err(LemmyErrorType::ContradictingFilters)?
} else {
Ok(())
}
}
pub async fn process_markdown( pub async fn process_markdown(
text: &str, text: &str,
slur_regex: &Option<Regex>, slur_regex: &Option<Regex>,

View file

@ -87,7 +87,8 @@ pub async fn get_post(
// Fetch the cross_posts // Fetch the cross_posts
let cross_posts = if let Some(url) = &post_view.post.url { let cross_posts = if let Some(url) = &post_view.post.url {
let mut x_posts = PostQuery { 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(), local_user: local_user.as_ref(),
..Default::default() ..Default::default()
} }

View file

@ -8,14 +8,14 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{GetPosts, GetPostsResponse}, 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_schema::source::community::Community;
use lemmy_db_views::{ use lemmy_db_views::{
post_view::PostQuery, post_view::PostQuery,
structs::{LocalUserView, PaginationCursor, SiteView}, structs::{LocalUserView, PaginationCursor, SiteView},
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn list_posts( pub async fn list_posts(
@ -45,9 +45,7 @@ pub async fn list_posts(
let liked_only = data.liked_only; let liked_only = data.liked_only;
let disliked_only = data.disliked_only; let disliked_only = data.disliked_only;
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() { check_conflicting_like_filters(liked_only, disliked_only)?;
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
}
let local_user = local_user_view.as_ref().map(|u| &u.local_user); let local_user = local_user_view.as_ref().map(|u| &u.local_user);
let listing_type = Some(listing_type_with_default( let listing_type = Some(listing_type_with_default(

View file

@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
site::{Search, SearchResponse}, 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_schema::{source::community::Community, utils::post_to_comment_sort_type, SearchType};
use lemmy_db_views::{ use lemmy_db_views::{
@ -37,67 +37,87 @@ pub async fn search(
// TODO no clean / non-nsfw searching rn // TODO no clean / non-nsfw searching rn
let q = data.q.clone(); let Query(Search {
let page = data.page; q,
let limit = data.limit; community_id,
let sort = data.sort; community_name,
let listing_type = data.listing_type; creator_id,
let search_type = data.type_.unwrap_or(SearchType::All); type_,
let community_id = if let Some(name) = &data.community_name { 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( Some(
resolve_actor_identifier::<ApubCommunity, Community>(name, &context, &local_user_view, false) resolve_actor_identifier::<ApubCommunity, Community>(name, &context, &local_user_view, false)
.await?, .await?,
) )
.map(|c| c.id) .map(|c| c.id)
} else { } 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 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 { let posts_query = PostQuery {
sort: (sort), sort,
listing_type: (listing_type), listing_type,
community_id: (community_id), community_id,
creator_id: (creator_id), creator_id,
local_user, local_user,
search_term: (Some(q.clone())), search_term: Some(q.clone()),
page: (page), page,
limit: (limit), limit,
title_only: (post_title_only), title_only: post_title_only,
url_only: post_url_only,
liked_only,
disliked_only,
saved_only,
..Default::default() ..Default::default()
}; };
let comment_query = CommentQuery { let comment_query = CommentQuery {
sort: (sort.map(post_to_comment_sort_type)), sort: sort.map(post_to_comment_sort_type),
listing_type: (listing_type), listing_type,
search_term: (Some(q.clone())), search_term: Some(q.clone()),
community_id: (community_id), community_id,
creator_id: (creator_id), creator_id,
local_user, local_user,
page: (page), page,
limit: (limit), limit,
liked_only,
disliked_only,
saved_only,
..Default::default() ..Default::default()
}; };
let community_query = CommunityQuery { let community_query = CommunityQuery {
sort: (sort), sort,
listing_type: (listing_type), listing_type,
search_term: (Some(q.clone())), search_term: Some(q.clone()),
local_user, local_user,
is_mod_or_admin: (is_admin), is_mod_or_admin: is_admin,
page: (page), page,
limit: (limit), limit,
..Default::default() ..Default::default()
}; };
let person_query = PersonQuery { let person_query = PersonQuery {
sort, sort,
search_term: (Some(q.clone())), search_term: Some(q.clone()),
listing_type: (listing_type), listing_type,
page: (page), page,
limit: (limit), limit,
}; };
match search_type { match search_type {
@ -120,7 +140,7 @@ pub async fn search(
SearchType::All => { SearchType::All => {
// If the community or creator is included, dont search communities or users // If the community or creator is included, dont search communities or users
let community_or_creator_included = 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 posts = posts_query
.list(&local_site.site, &mut context.pool()) .list(&local_site.site, &mut context.pool())
@ -142,21 +162,6 @@ pub async fn search(
person_query.list(&mut context.pool()).await? 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 // Return the jwt

View file

@ -182,7 +182,6 @@ pub enum SearchType {
Posts, Posts,
Communities, Communities,
Users, Users,
Url,
} }
#[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)] #[derive(EnumString, Display, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)]

View file

@ -382,11 +382,10 @@ fn queries<'a>() -> Queries<
query = query.filter(community::hidden.eq(false)); 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 { if let Some(search_term) = &options.search_term {
if options.url_only.unwrap_or_default() {
query = query.filter(post::url.eq(search_term));
} else {
let searcher = fuzzy_search(search_term); let searcher = fuzzy_search(search_term);
query = if options.title_only.unwrap_or_default() { query = if options.title_only.unwrap_or_default() {
query.filter(post::name.ilike(searcher)) query.filter(post::name.ilike(searcher))
@ -399,6 +398,7 @@ fn queries<'a>() -> Queries<
} }
.filter(not(post::removed.or(post::deleted))); .filter(not(post::removed.or(post::deleted)));
} }
}
if !options if !options
.show_nsfw .show_nsfw
@ -616,7 +616,7 @@ pub struct PostQuery<'a> {
pub community_id_just_for_prefetch: bool, pub community_id_just_for_prefetch: bool,
pub local_user: Option<&'a LocalUser>, pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>, pub search_term: Option<String>,
pub url_search: Option<String>, pub url_only: Option<bool>,
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
pub liked_only: Option<bool>, pub liked_only: Option<bool>,
pub disliked_only: Option<bool>, pub disliked_only: Option<bool>,
@ -765,10 +765,20 @@ mod tests {
local_user_vote_display_mode::LocalUserVoteDisplayMode, local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
person_block::{PersonBlock, PersonBlockForm}, person_block::{PersonBlock, PersonBlockForm},
post::{Post, PostHide, PostInsertForm, PostLike, PostLikeForm, PostRead, PostUpdateForm}, post::{
Post,
PostHide,
PostInsertForm,
PostLike,
PostLikeForm,
PostRead,
PostSaved,
PostSavedForm,
PostUpdateForm,
},
site::Site, 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}, utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT},
CommunityVisibility, CommunityVisibility,
PostSortType, PostSortType,
@ -1215,6 +1225,36 @@ mod tests {
cleanup(data, pool).await 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] #[tokio::test]
#[serial] #[serial]
async fn creator_info() -> LemmyResult<()> { async fn creator_info() -> LemmyResult<()> {