diff --git a/migrations/20230426221940_init.up.sql b/migrations/20230426221940_init.up.sql index f94e598..5cf418d 100644 --- a/migrations/20230426221940_init.up.sql +++ b/migrations/20230426221940_init.up.sql @@ -18,7 +18,7 @@ create table if not exists witches ( -- table of things to watch create table if not exists watches ( id blob not null primary key, - typ int not null, -- enum for movie or tv show or whatev + kind int not null, -- enum for movie or tv show or whatev title text not null, metadata_url text, -- possible url for imdb or other metadata-esque site to show the user length int, diff --git a/src/lib.rs b/src/lib.rs index c07ca09..9bf07e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,9 +38,7 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout use generic_handlers::{handle_slash, handle_slash_redir}; use login::{get_login, get_logout, post_login, post_logout}; use signup::{get_create_user, handle_signup_success, post_create_user}; - use watches::handlers::{ - get_search_watch, get_watches, post_add_watch, post_search_watch, put_add_watch, - }; + use watches::handlers::{get_search_watch, get_watches, post_add_watch}; axum::Router::new() .route("/", get(handle_slash).post(handle_slash)) @@ -51,13 +49,8 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout .route("/watches", get(get_watches)) .route("/watch", get(get_watch)) .route("/watch/:id", get(get_watch)) - .route("/search", get(get_search_watch).post(post_search_watch)) - .route( - "/add", - get(get_search_watch) - .put(put_add_watch) - .post(post_add_watch), - ) + .route("/search", get(get_search_watch)) + .route("/add", get(get_search_watch).post(post_add_watch)) .fallback(handle_slash_redir) .layer(middleware::from_fn_with_state( db_pool.clone(), diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs index d220ee5..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,39 +53,39 @@ 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, } //-************************************************************************ // handlers //-************************************************************************ -/// Add a new Watch to the whole system (also adds to your watchlist) -pub async fn put_add_watch() {} - -/// Add a Watch to your watchlist -pub async fn post_add_watch() {} +/// Add a Watch to your watchlist (side effects system-add if missing) +pub async fn post_add_watch( + auth: AuthContext, + State(pool): State, + Form(form): Form, +) -> impl IntoResponse { +} /// A single Watch pub async fn get_watch( @@ -92,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) @@ -109,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 @@ -123,27 +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, } } -pub async fn post_search_watch() {} +//-************************************************************************ +// 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/mod.rs b/src/watches/mod.rs index 2dd680b..ecfc31a 100644 --- a/src/watches/mod.rs +++ b/src/watches/mod.rs @@ -7,7 +7,7 @@ pub mod templates; #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, sqlx::Type, )] -#[repr(i32)] +#[repr(i64)] pub enum ShowKind { Movie = 0, Series = 1, @@ -18,7 +18,14 @@ pub enum ShowKind { impl std::fmt::Display for ShowKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + let repr = match self { + Self::Movie => "movie", + Self::Series => "series", + Self::LimitedSeries => "limited series", + Self::Short => "short form", + Self::Unknown => "unknown", + }; + write!(f, "{repr}") } } @@ -28,8 +35,8 @@ impl Default for ShowKind { } } -impl From for ShowKind { - fn from(value: i32) -> Self { +impl From for ShowKind { + fn from(value: i64) -> Self { match value { 0 => Self::Movie, 1 => Self::Series, @@ -59,7 +66,7 @@ pub struct Watch { pub kind: ShowKind, pub title: String, pub metadata_url: Option, - pub length: Option, + pub length: Option, pub release_date: Option, added_by: Uuid, // this shouldn't be exposed to randos created_at: i64, 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 %}