diff --git a/src/lib.rs b/src/lib.rs index 9bf07e5..8ff06b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,10 @@ 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}; + use watches::handlers::{ + get_add_new_watch, get_search_watch, get_watches, post_add_existing_watch, + post_add_new_watch, + }; axum::Router::new() .route("/", get(handle_slash).post(handle_slash)) @@ -50,7 +53,11 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout .route("/watch", get(get_watch)) .route("/watch/:id", get(get_watch)) .route("/search", get(get_search_watch)) - .route("/add", get(get_search_watch).post(post_add_watch)) + .route("/add", get(get_add_new_watch).post(post_add_new_watch)) + .route( + "/add/watch", + get(get_search_watch).post(post_add_existing_watch), + ) .fallback(handle_slash_redir) .layer(middleware::from_fn_with_state( db_pool.clone(), diff --git a/src/signup.rs b/src/signup.rs index a2e73f0..3fe24b4 100644 --- a/src/signup.rs +++ b/src/signup.rs @@ -160,14 +160,7 @@ pub(crate) async fn create_user( .bind(email) .bind(&pwhash); - let res = { - let txn = pool.begin().await.expect("Could not beign transaction"); - let r = query.execute(pool).await; - txn.commit() - .await - .expect("Should be able to commit transaction"); - r - }; + let res = query.execute(pool).await; match res { Ok(_) => { diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs index 2bb1e4b..2696e80 100644 --- a/src/watches/handlers.rs +++ b/src/watches/handlers.rs @@ -1,13 +1,13 @@ use axum::{ extract::{Form, Path, Query, State}, http::StatusCode, - response::{IntoResponse, Response}, + response::{IntoResponse, Redirect, Response}, }; use serde::{de, Deserialize, Deserializer}; -use sqlx::{query_as, SqlitePool}; +use sqlx::{query, query_as, SqlitePool}; use uuid::Uuid; -use super::templates::{GetSearchWatches, GetWatch}; +use super::templates::{GetAddNewWatch, GetSearchWatches, GetWatch}; use crate::{AuthContext, GetWatches, ShowKind, Watch}; //-************************************************************************ @@ -19,7 +19,11 @@ const GET_SAVED_WATCHES_QUERY: &str = const GET_WATCH_QUERY: &str = "select * from watches where id = $1"; -const EMPTY_SEARCH_QUERY: SearchQuery = SearchQuery { +const ADD_WATCH_QUERY: &str = "insert into watches (id, title, kind, release_date, metadata_url, added_by) values ($1, $2, $3, $4, $5, $6)"; +const ADD_WITCH_WATCH_QUERY: &str = + "insert into witch_watch (id, witch, watch, public, watched) values ($1, $2, $3, $4, $5)"; + +const EMPTY_SEARCH_QUERY_STRUCT: SearchQuery = SearchQuery { title: None, kind: None, year: None, @@ -37,20 +41,26 @@ pub struct WatchAddError(#[from] WatchAddErrorKind); #[non_exhaustive] pub enum WatchAddErrorKind { UnknownDBError, + NotSignedIn, } impl IntoResponse for WatchAddError { fn into_response(self) -> Response { - match self.0 { + match &self.0 { WatchAddErrorKind::UnknownDBError => { (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response() } + WatchAddErrorKind::NotSignedIn => ( + StatusCode::OK, + "Ope, you need to sign in first!".to_string(), + ) + .into_response(), } } } //-************************************************************************ -// Types for receiving arguments from search queries +// Types for receiving arguments from forms //-************************************************************************ #[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] @@ -67,23 +77,92 @@ pub struct SearchQuery { // kinda the main form? #[derive(Debug, Default, Deserialize, PartialEq, Eq)] -pub struct PostAddWatch { - pub id: Option, // maybe this already exists +pub struct PostAddNewWatch { pub title: String, + #[serde(default)] + pub private: bool, pub kind: Option, - pub release_date: Option, // need a date-picker or something + pub year: Option, // need a date-picker or something pub metadata_url: Option, + #[serde(default)] + pub watched_already: bool, +} + +#[derive(Debug, Default, Deserialize, PartialEq, Eq)] +pub struct PostAddExistingWatch { + pub id: String, + pub public: bool, + pub watched_already: bool, } //-************************************************************************ // handlers //-************************************************************************ -/// Add a Watch to your watchlist (side effects system-add if missing) -pub async fn post_add_watch( +pub async fn get_add_new_watch(auth: AuthContext) -> impl IntoResponse { + GetAddNewWatch { + user: auth.current_user, + } +} + +/// Add a Watch to your watchlist (side effects system-add) +pub async fn post_add_new_watch( auth: AuthContext, State(pool): State, - Form(form): Form, + Form(form): Form, +) -> Result { + if let Some(user) = auth.current_user { + { + let watch_id = Uuid::new_v4(); + let witch_watch_id = Uuid::new_v4(); + let mut tx = pool + .begin() + .await + .map_err(|_| WatchAddErrorKind::UnknownDBError)?; + query(ADD_WATCH_QUERY) + .bind(watch_id) + .bind(&form.title) + .bind(form.kind) + .bind(&form.year) + .bind(form.metadata_url) + .bind(user.id) + .execute(&mut tx) + .await + .map_err(|err| { + tracing::error!("Got error: {err}"); + WatchAddErrorKind::UnknownDBError + })?; + + query(ADD_WITCH_WATCH_QUERY) + .bind(witch_watch_id) + .bind(user.id) + .bind(watch_id) + .bind(!form.private) + .bind(form.watched_already) + .execute(&mut tx) + .await + .map_err(|err| { + tracing::error!("Got error: {err}"); + WatchAddErrorKind::UnknownDBError + })?; + tx.commit().await.map_err(|err| { + tracing::error!("Got error: {err}"); + WatchAddErrorKind::UnknownDBError + })?; + let id = watch_id.simple(); + let location = format!("/watch/{id}"); + Ok(Redirect::to(&location)) + } + } else { + Err(WatchAddErrorKind::NotSignedIn.into()) + } +} + +/// Add a Watch to your watchlist by selecting it with a checkbox +pub async fn post_add_existing_watch( + _auth: AuthContext, + State(_pool): State, + Form(_form): Form, ) -> impl IntoResponse { } @@ -135,7 +214,7 @@ pub async fn get_search_watch( ) -> impl IntoResponse { let user = auth.current_user; - let search = if search.0 != EMPTY_SEARCH_QUERY { + let search = if search.0 != EMPTY_SEARCH_QUERY_STRUCT { let s = search.0; format!("{s:?}") } else { diff --git a/src/watches/mod.rs b/src/watches/mod.rs index ecfc31a..5107164 100644 --- a/src/watches/mod.rs +++ b/src/watches/mod.rs @@ -14,6 +14,7 @@ pub enum ShowKind { LimitedSeries = 2, Short = 3, Unknown = 4, + Other = 5, } impl std::fmt::Display for ShowKind { @@ -23,6 +24,7 @@ impl std::fmt::Display for ShowKind { Self::Series => "series", Self::LimitedSeries => "limited series", Self::Short => "short form", + Self::Other => "other", Self::Unknown => "unknown", }; write!(f, "{repr}") @@ -43,6 +45,7 @@ impl From for ShowKind { 2 => Self::LimitedSeries, 3 => Self::Short, 4 => Self::Unknown, + 5 => Self::Other, _ => Self::Unknown, } } diff --git a/src/watches/templates.rs b/src/watches/templates.rs index ab9358f..2cd4651 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, ShowKind, User, Watch}; +use crate::{OptionalOptionalUser, User, Watch}; #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "get_watches.html")] @@ -24,3 +24,9 @@ pub struct GetWatch { pub watch: Option, pub user: Option, } + +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] +#[template(path = "get_add_new_watch.html")] +pub struct GetAddNewWatch { + pub user: Option, +} diff --git a/templates/get_add_new_watch.html b/templates/get_add_new_watch.html new file mode 100644 index 0000000..ddb455b --- /dev/null +++ b/templates/get_add_new_watch.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% import "macros.html" as m %} + +{% block title %}Welcome to Witch Watch, Bish{% endblock %} + +{% block content %} + +

Add something to watch!

+ +{% if user.is_some() %} + +
+
+ +
+ + +
+ + + + + +
+ + +
+ + +
+ + +
+ + +
+ +{% else %} + +
+ Oh dang, you need to login or signup to add something to watch! + +
+ +{% endif %} + +{% endblock %}