add working 'add new watch' handler
This commit is contained in:
parent
78b77dbd30
commit
cea9af1112
6 changed files with 166 additions and 24 deletions
11
src/lib.rs
11
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 generic_handlers::{handle_slash, handle_slash_redir};
|
||||||
use login::{get_login, get_logout, post_login, post_logout};
|
use login::{get_login, get_logout, post_login, post_logout};
|
||||||
use signup::{get_create_user, handle_signup_success, post_create_user};
|
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()
|
axum::Router::new()
|
||||||
.route("/", get(handle_slash).post(handle_slash))
|
.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", get(get_watch))
|
||||||
.route("/watch/:id", get(get_watch))
|
.route("/watch/:id", get(get_watch))
|
||||||
.route("/search", get(get_search_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)
|
.fallback(handle_slash_redir)
|
||||||
.layer(middleware::from_fn_with_state(
|
.layer(middleware::from_fn_with_state(
|
||||||
db_pool.clone(),
|
db_pool.clone(),
|
||||||
|
|
|
@ -160,14 +160,7 @@ pub(crate) async fn create_user(
|
||||||
.bind(email)
|
.bind(email)
|
||||||
.bind(&pwhash);
|
.bind(&pwhash);
|
||||||
|
|
||||||
let res = {
|
let res = query.execute(pool).await;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Form, Path, Query, State},
|
extract::{Form, Path, Query, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
};
|
};
|
||||||
use serde::{de, Deserialize, Deserializer};
|
use serde::{de, Deserialize, Deserializer};
|
||||||
use sqlx::{query_as, SqlitePool};
|
use sqlx::{query, query_as, SqlitePool};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::templates::{GetSearchWatches, GetWatch};
|
use super::templates::{GetAddNewWatch, GetSearchWatches, GetWatch};
|
||||||
use crate::{AuthContext, GetWatches, ShowKind, Watch};
|
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 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,
|
title: None,
|
||||||
kind: None,
|
kind: None,
|
||||||
year: None,
|
year: None,
|
||||||
|
@ -37,20 +41,26 @@ pub struct WatchAddError(#[from] WatchAddErrorKind);
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum WatchAddErrorKind {
|
pub enum WatchAddErrorKind {
|
||||||
UnknownDBError,
|
UnknownDBError,
|
||||||
|
NotSignedIn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for WatchAddError {
|
impl IntoResponse for WatchAddError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
match self.0 {
|
match &self.0 {
|
||||||
WatchAddErrorKind::UnknownDBError => {
|
WatchAddErrorKind::UnknownDBError => {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
(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)]
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
@ -67,23 +77,92 @@ pub struct SearchQuery {
|
||||||
|
|
||||||
// kinda the main form?
|
// kinda the main form?
|
||||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
||||||
pub struct PostAddWatch {
|
pub struct PostAddNewWatch {
|
||||||
pub id: Option<String>, // maybe this already exists
|
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub private: bool,
|
||||||
pub kind: Option<ShowKind>,
|
pub kind: Option<ShowKind>,
|
||||||
pub release_date: Option<String>, // need a date-picker or something
|
pub year: Option<String>, // need a date-picker or something
|
||||||
pub metadata_url: Option<String>,
|
pub metadata_url: Option<String>,
|
||||||
|
#[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
|
// handlers
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
|
||||||
/// Add a Watch to your watchlist (side effects system-add if missing)
|
pub async fn get_add_new_watch(auth: AuthContext) -> impl IntoResponse {
|
||||||
pub async fn post_add_watch(
|
GetAddNewWatch {
|
||||||
|
user: auth.current_user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a Watch to your watchlist (side effects system-add)
|
||||||
|
pub async fn post_add_new_watch(
|
||||||
auth: AuthContext,
|
auth: AuthContext,
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
Form(form): Form<PostAddWatch>,
|
Form(form): Form<PostAddNewWatch>,
|
||||||
|
) -> Result<impl IntoResponse, WatchAddError> {
|
||||||
|
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<SqlitePool>,
|
||||||
|
Form(_form): Form<PostAddExistingWatch>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +214,7 @@ pub async fn get_search_watch(
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let user = auth.current_user;
|
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;
|
let s = search.0;
|
||||||
format!("{s:?}")
|
format!("{s:?}")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub enum ShowKind {
|
||||||
LimitedSeries = 2,
|
LimitedSeries = 2,
|
||||||
Short = 3,
|
Short = 3,
|
||||||
Unknown = 4,
|
Unknown = 4,
|
||||||
|
Other = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ShowKind {
|
impl std::fmt::Display for ShowKind {
|
||||||
|
@ -23,6 +24,7 @@ impl std::fmt::Display for ShowKind {
|
||||||
Self::Series => "series",
|
Self::Series => "series",
|
||||||
Self::LimitedSeries => "limited series",
|
Self::LimitedSeries => "limited series",
|
||||||
Self::Short => "short form",
|
Self::Short => "short form",
|
||||||
|
Self::Other => "other",
|
||||||
Self::Unknown => "unknown",
|
Self::Unknown => "unknown",
|
||||||
};
|
};
|
||||||
write!(f, "{repr}")
|
write!(f, "{repr}")
|
||||||
|
@ -43,6 +45,7 @@ impl From<i64> for ShowKind {
|
||||||
2 => Self::LimitedSeries,
|
2 => Self::LimitedSeries,
|
||||||
3 => Self::Short,
|
3 => Self::Short,
|
||||||
4 => Self::Unknown,
|
4 => Self::Unknown,
|
||||||
|
5 => Self::Other,
|
||||||
_ => Self::Unknown,
|
_ => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{OptionalOptionalUser, ShowKind, User, Watch};
|
use crate::{OptionalOptionalUser, User, Watch};
|
||||||
|
|
||||||
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||||
#[template(path = "get_watches.html")]
|
#[template(path = "get_watches.html")]
|
||||||
|
@ -24,3 +24,9 @@ pub struct GetWatch {
|
||||||
pub watch: Option<Watch>,
|
pub watch: Option<Watch>,
|
||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||||
|
#[template(path = "get_add_new_watch.html")]
|
||||||
|
pub struct GetAddNewWatch {
|
||||||
|
pub user: Option<User>,
|
||||||
|
}
|
||||||
|
|
54
templates/get_add_new_watch.html
Normal file
54
templates/get_add_new_watch.html
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% import "macros.html" as m %}
|
||||||
|
|
||||||
|
{% block title %}Welcome to Witch Watch, Bish{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Add something to watch!</h1>
|
||||||
|
|
||||||
|
{% if user.is_some() %}
|
||||||
|
|
||||||
|
<div class="add-watch">
|
||||||
|
<form action="/add" enctype="application/x-www-form-urlencoded" method="post">
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" id="title"></br>
|
||||||
|
|
||||||
|
<label for="year">Release Year</label>
|
||||||
|
<input type="text" name="year" id="year"></br>
|
||||||
|
|
||||||
|
<label for="kind">Type</label>
|
||||||
|
<select id="kind" name="kind">
|
||||||
|
<option value="Unknown">Unknown</option>
|
||||||
|
<option value="Movie">Movie</option>
|
||||||
|
<option value="Series">Series</option>
|
||||||
|
<option value="LimitedSeries">Limited Series</option>
|
||||||
|
<option value="Short">Short</option>
|
||||||
|
<option value="Other">Other</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="is-private">Private to you (default is public)?</label>
|
||||||
|
<input type="checkbox" name="private" id="is-private" value="true"></br>
|
||||||
|
|
||||||
|
<label for="is-watched">Have you already watched this?</label>
|
||||||
|
<input type="checkbox" name="watched_already" id="is-watched" value="true" default="false"></br>
|
||||||
|
|
||||||
|
<label for="md-url">Metadata URL (TMDB, OMDB, IMDb, etc.)</label>
|
||||||
|
<input type="text" name="metadata_url" id="md-url"></br>
|
||||||
|
|
||||||
|
<input type="submit" value="Let's go!">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div class="add-watch">
|
||||||
|
<span class="not-logged-in">Oh dang, you need to <a href="/login">login</a> or <a href="/signup">signup</a> to add something to watch!</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue