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 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(),
|
||||
|
|
|
@ -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(_) => {
|
||||
|
|
|
@ -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<String>, // maybe this already exists
|
||||
pub struct PostAddNewWatch {
|
||||
pub title: String,
|
||||
#[serde(default)]
|
||||
pub private: bool,
|
||||
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>,
|
||||
#[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<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 {
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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<i64> for ShowKind {
|
|||
2 => Self::LimitedSeries,
|
||||
3 => Self::Short,
|
||||
4 => Self::Unknown,
|
||||
5 => Self::Other,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Watch>,
|
||||
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