checkpoint merge from watches

This commit is contained in:
Joe Ardent 2023-06-19 16:56:49 -07:00
commit a40794242b
6 changed files with 166 additions and 24 deletions

View file

@ -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(),

View file

@ -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(_) => {

View file

@ -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 {

View file

@ -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,
} }
} }

View file

@ -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>,
}

View 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 %}