diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs index 910181f..2bb1e4b 100644 --- a/src/watches/handlers.rs +++ b/src/watches/handlers.rs @@ -3,22 +3,29 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use serde::Deserialize; +use serde::{de, Deserialize, Deserializer}; use sqlx::{query_as, SqlitePool}; use uuid::Uuid; use super::templates::{GetSearchWatches, GetWatch}; -use crate::{AuthContext, GetWatches, ShowKind, User, Watch}; +use crate::{AuthContext, GetWatches, ShowKind, Watch}; //-************************************************************************ // Constants //-************************************************************************ -const GET_WATCHES_QUERY: &str = +const GET_SAVED_WATCHES_QUERY: &str = "select * from watches left join witch_watch on $1 = witch_watch.witch and watches.id = witch_watch.watch"; const GET_WATCH_QUERY: &str = "select * from watches where id = $1"; +const EMPTY_SEARCH_QUERY: SearchQuery = SearchQuery { + title: None, + kind: None, + year: None, + search: None, +}; + //-************************************************************************ // Error types for Watch creation //-************************************************************************ @@ -46,28 +53,26 @@ impl IntoResponse for WatchAddError { // Types for receiving arguments from search queries //-************************************************************************ -#[derive(Debug, Default, Clone, Deserialize)] -pub struct SimpleSearchQuery { - search: String, -} - -#[derive(Debug, Default, Clone, Deserialize)] -pub struct FullSearchQuery { +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] +pub struct SearchQuery { + #[serde(default, deserialize_with = "empty_string_as_none")] + pub search: Option, + #[serde(default, deserialize_with = "empty_string_as_none")] pub title: Option, + #[serde(default, deserialize_with = "empty_string_as_none")] pub kind: Option, + #[serde(default, deserialize_with = "empty_string_as_none")] pub year: Option, } -#[derive(Debug, Clone, Deserialize)] -pub enum SearchQuery { - Full(FullSearchQuery), - Simple(SimpleSearchQuery), -} - -impl Default for SearchQuery { - fn default() -> Self { - SearchQuery::Simple(SimpleSearchQuery::default()) - } +// kinda the main form? +#[derive(Debug, Default, Deserialize, PartialEq, Eq)] +pub struct PostAddWatch { + pub id: Option, // maybe this already exists + pub title: String, + pub kind: Option, + pub release_date: Option, // need a date-picker or something + pub metadata_url: Option, } //-************************************************************************ @@ -75,7 +80,12 @@ impl Default for SearchQuery { //-************************************************************************ /// Add a Watch to your watchlist (side effects system-add if missing) -pub async fn post_add_watch() -> impl IntoResponse {} +pub async fn post_add_watch( + auth: AuthContext, + State(pool): State, + Form(form): Form, +) -> impl IntoResponse { +} /// A single Watch pub async fn get_watch( @@ -89,7 +99,7 @@ pub async fn get_watch( "".to_string() }; let id = id.trim(); - + let id = Uuid::try_parse(id).unwrap_or_default(); let watch: Option = query_as(GET_WATCH_QUERY) .bind(id) .fetch_one(&pool) @@ -106,7 +116,7 @@ pub async fn get_watch( pub async fn get_watches(auth: AuthContext, State(pool): State) -> impl IntoResponse { let user = auth.current_user; let watches: Vec = if (user).is_some() { - query_as(GET_WATCHES_QUERY) + query_as(GET_SAVED_WATCHES_QUERY) .bind(user.as_ref().unwrap().id) .fetch_all(&pool) .await @@ -120,28 +130,47 @@ pub async fn get_watches(auth: AuthContext, State(pool): State) -> i pub async fn get_search_watch( auth: AuthContext, - State(_pool): State, - search: Option>, + State(pool): State, + search: Query, ) -> impl IntoResponse { - use SearchQuery::*; - let search = match search { - Some(Query(Simple(SimpleSearchQuery { search }))) => search, - Some(Query(Full(q))) => { - // obviously this is dumb - format!("{q:?}") - } - None => "".to_owned(), - }; - let search = search.trim().to_string(); - let user = auth.current_user; + let search = if search.0 != EMPTY_SEARCH_QUERY { + let s = search.0; + format!("{s:?}") + } else { + "".to_string() + }; + + // until tantivy search + let watches: Vec = query_as("select * from watches") + .fetch_all(&pool) + .await + .unwrap_or_default(); + GetSearchWatches { - watches: vec![], + watches, user, search, } } +//-************************************************************************ +// helper fns +//-************************************************************************ +/// Serde deserialization decorator to map empty Strings to None, +fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: std::str::FromStr, + T::Err: std::fmt::Display, +{ + let opt = Option::::deserialize(de)?; + match opt.as_deref() { + None | Some("") => Ok(None), + Some(s) => std::str::FromStr::from_str(s) + .map_err(de::Error::custom) + .map(Some), + } } diff --git a/src/watches/templates.rs b/src/watches/templates.rs index 1f4525d..ab9358f 100644 --- a/src/watches/templates.rs +++ b/src/watches/templates.rs @@ -1,7 +1,7 @@ use askama::Template; use serde::{Deserialize, Serialize}; -use crate::{OptionalOptionalUser, User, Watch}; +use crate::{OptionalOptionalUser, ShowKind, User, Watch}; #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "get_watches.html")] diff --git a/templates/get_search_watches.html b/templates/get_search_watches.html index 6835865..fe3bb8e 100644 --- a/templates/get_search_watches.html +++ b/templates/get_search_watches.html @@ -16,12 +16,35 @@ {% endfor %} -

+

Simple Search

+

+ +
+

Fussy Search

+
+ +
+ + +
+ + + + -

+ + +
{% endblock %}