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

View file

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

View file

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

View file

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

View file

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

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