Dont allow empty search queries, error handling for search

This commit is contained in:
Felix Ableitner 2024-02-12 12:16:59 +01:00
parent 05d260d463
commit 575ef14a23
3 changed files with 41 additions and 18 deletions

View File

@ -235,6 +235,9 @@ pub(super) async fn search_article(
Query(query): Query<SearchArticleData>, Query(query): Query<SearchArticleData>,
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<Json<Vec<DbArticle>>> { ) -> MyResult<Json<Vec<DbArticle>>> {
if query.query.is_empty() {
return Err(anyhow!("Query is empty").into());
}
let article = DbArticle::search(&query.query, &data.db_connection)?; let article = DbArticle::search(&query.query, &data.db_connection)?;
Ok(Json(article)) Ok(Json(article))
} }

View File

@ -18,8 +18,8 @@ pub fn Nav() -> impl IntoView {
GlobalState::api_client() GlobalState::api_client()
.get_local_instance() .get_local_instance()
.await .await
.unwrap() .map(|i| i.registration_open)
.registration_open .unwrap_or_default()
}, },
); );
@ -43,7 +43,9 @@ pub fn Nav() -> impl IntoView {
ev.prevent_default(); ev.prevent_default();
let navigate = leptos_router::use_navigate(); let navigate = leptos_router::use_navigate();
let query = search_query.get(); let query = search_query.get();
navigate(&format!("/search?query={query}"), Default::default()); if !query.is_empty() {
navigate(&format!("/search?query={query}"), Default::default());
}
}> }>
<input type="text" placeholder="Search" <input type="text" placeholder="Search"
prop:value=search_query prop:value=search_query

View File

@ -1,42 +1,51 @@
use crate::common::{DbArticle, DbInstance, SearchArticleData}; use crate::common::{DbArticle, DbInstance, SearchArticleData};
use crate::frontend::app::GlobalState; use crate::frontend::app::GlobalState;
use crate::frontend::{article_link, article_title}; use crate::frontend::{article_link, article_title};
use futures::join;
use leptos::*; use leptos::*;
use leptos_router::use_query_map; use leptos_router::use_query_map;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
#[derive(Default, Clone, Deserialize, Serialize)] #[derive(Default, Clone, Deserialize, Serialize, Debug)]
struct SearchResults { struct SearchResults {
articles: Vec<DbArticle>, articles: Vec<DbArticle>,
instance: Option<DbInstance>, instance: Option<DbInstance>,
} }
impl SearchResults {
pub fn is_empty(&self) -> bool {
self.articles.is_empty() && self.instance.is_none()
}
}
#[component] #[component]
pub fn Search() -> impl IntoView { pub fn Search() -> impl IntoView {
let params = use_query_map(); let params = use_query_map();
let query = move || params.get().get("query").cloned().unwrap(); let query = move || params.get().get("query").cloned().unwrap();
let (error, set_error) = create_signal(None::<String>);
let search_results = create_resource(query, move |query| async move { let search_results = create_resource(query, move |query| async move {
set_error.set(None);
let mut search_results = SearchResults::default(); let mut search_results = SearchResults::default();
let api_client = GlobalState::api_client(); let api_client = GlobalState::api_client();
let url = Url::parse(&query); let url = Url::parse(&query);
let search_data = SearchArticleData { query }; let search_data = SearchArticleData { query };
let search = api_client.search(&search_data); let search = api_client.search(&search_data);
match search.await {
Ok(mut a) => search_results.articles.append(&mut a),
Err(e) => set_error.set(Some(e.0.to_string())),
}
// If its a valid url, also attempt to resolve as federation object // If its a valid url, also attempt to resolve as federation object
if let Ok(url) = url { if let Ok(url) = url {
let resolve_article = api_client.resolve_article(url.clone()); match api_client.resolve_article(url.clone()).await {
let resolve_instance = api_client.resolve_instance(url); Ok(a) => search_results.articles.push(a.article),
let (search, resolve_article, resolve_instance) = Err(e) => set_error.set(Some(e.0.to_string())),
join!(search, resolve_article, resolve_instance); }
search_results.instance = resolve_instance.ok(); match api_client.resolve_instance(url).await {
if let Ok(article) = resolve_article { Ok(a) => search_results.instance = Some(a),
search_results.articles.push(article.article); Err(e) => set_error.set(Some(e.0.to_string())),
} }
search_results.articles.append(&mut search.unwrap())
} else {
search_results.articles.append(&mut search.await.unwrap())
} }
search_results search_results
}); });
@ -44,11 +53,20 @@ pub fn Search() -> impl IntoView {
view! { view! {
<h1>"Search results for "{query}</h1> <h1>"Search results for "{query}</h1>
<Suspense fallback=|| view! { "Loading..." }> { <Suspense fallback=|| view! { "Loading..." }> {
move || search_results.get().map(|search_results| { move || search_results.get().map(move |search_results| {
let is_empty = search_results.articles.is_empty() && search_results.instance.is_none(); let is_empty = search_results.is_empty();
view! { view! {
<Show when=move || !is_empty <Show when=move || !is_empty
fallback=|| view! { <p>No results found</p> }> fallback=move || {
let error_view = move || {
error.get().map(|err| {
view! { <p style="color:red;">{err}</p> }
})
};
view! {
{error_view}
<p>No results found</p>
}}>
<ul> <ul>
{ {
// render resolved instance // render resolved instance