clickable htmx button for adding search results to your list

This commit is contained in:
Joe Ardent 2023-12-31 13:55:16 -08:00
parent 1c304e1184
commit c4976a3efc
4 changed files with 55 additions and 56 deletions

View file

@ -8,7 +8,7 @@ table {
} }
th { th {
background-color: darkgray; background-color: ghostwhite;
} }
th, td { th, td {
@ -16,7 +16,7 @@ th, td {
padding: 8px; padding: 8px;
} }
tr:nth-child(odd) {background-color: ghostwhite;} tr:nth-child(even) {background-color: ghostwhite;}
#header { #header {
text-align: end; text-align: end;

View file

@ -3,6 +3,7 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Redirect, Response}, response::{IntoResponse, Redirect, Response},
}; };
use http::HeaderValue;
use julid::Julid; use julid::Julid;
use serde::Deserialize; use serde::Deserialize;
use sqlx::{query, query_as, query_scalar, SqlitePool}; use sqlx::{query, query_as, query_scalar, SqlitePool};
@ -37,22 +38,22 @@ const EMPTY_SEARCH_QUERY_STRUCT: SearchQuery = SearchQuery {
//-************************************************************************ //-************************************************************************
#[Error] #[Error]
pub struct WatchAddError(#[from] WatchAddErrorKind); pub struct AddError(#[from] AddErrorKind);
#[Error] #[Error]
#[non_exhaustive] #[non_exhaustive]
pub enum WatchAddErrorKind { pub enum AddErrorKind {
UnknownDBError, UnknownDBError,
NotSignedIn, NotSignedIn,
} }
impl IntoResponse for WatchAddError { impl IntoResponse for AddError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match &self.0 { match &self.0 {
WatchAddErrorKind::UnknownDBError => { AddErrorKind::UnknownDBError => {
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response() (StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
} }
WatchAddErrorKind::NotSignedIn => ( AddErrorKind::NotSignedIn => (
StatusCode::OK, StatusCode::OK,
"Ope, you need to sign in first!".to_string(), "Ope, you need to sign in first!".to_string(),
) )
@ -94,7 +95,7 @@ pub struct PostAddNewWatch {
#[derive(Debug, Default, Deserialize, PartialEq, Eq)] #[derive(Debug, Default, Deserialize, PartialEq, Eq)]
pub struct PostAddExistingWatch { pub struct PostAddExistingWatch {
pub id: String, pub watch: String,
pub public: bool, pub public: bool,
pub watched_already: bool, pub watched_already: bool,
} }
@ -107,18 +108,12 @@ pub async fn get_add_new_watch(auth: AuthSession) -> impl IntoResponse {
AddNewWatchPage { user: auth.user } AddNewWatchPage { user: auth.user }
} }
struct QuestQuest {
pub user: Julid,
pub is_public: bool,
pub already_watched: bool,
}
/// Add a Watch to your watchlist (side effects system-add) /// Add a Watch to your watchlist (side effects system-add)
pub async fn post_add_new_watch( pub async fn post_add_new_watch(
auth: AuthSession, auth: AuthSession,
State(pool): State<SqlitePool>, State(pool): State<SqlitePool>,
Form(form): Form<PostAddNewWatch>, Form(form): Form<PostAddNewWatch>,
) -> Result<impl IntoResponse, WatchAddError> { ) -> Result<impl IntoResponse, AddError> {
if let Some(user) = auth.user { if let Some(user) = auth.user {
{ {
let release_date = year_to_epoch(form.year.as_deref()); let release_date = year_to_epoch(form.year.as_deref());
@ -130,31 +125,27 @@ pub async fn post_add_new_watch(
added_by: user.id, added_by: user.id,
..Default::default() ..Default::default()
}; };
let quest = QuestQuest {
let watch_id = add_new_watch_impl(&pool, &watch).await?;
let quest = WatchQuest {
user: user.id, user: user.id,
is_public: !form.private, is_public: !form.private,
already_watched: form.watched_already, already_watched: form.watched_already,
watch: watch_id,
}; };
add_watch_quest_impl(&pool, &quest)
let watch_id = add_new_watch_impl(&pool, &watch, Some(quest)).await?; .await
.map_err(|_| AddErrorKind::UnknownDBError)?;
let location = format!("/watch/{watch_id}"); let location = format!("/watch/{watch_id}");
Ok(Redirect::to(&location)) Ok(Redirect::to(&location))
} }
} else { } else {
Err(WatchAddErrorKind::NotSignedIn.into()) Err(AddErrorKind::NotSignedIn.into())
} }
} }
async fn add_new_watch_impl( async fn add_new_watch_impl(db_pool: &SqlitePool, watch: &Watch) -> Result<Julid, AddError> {
db_pool: &SqlitePool,
watch: &Watch,
quest: Option<QuestQuest>,
) -> Result<Julid, WatchAddError> {
let mut tx = db_pool
.begin()
.await
.map_err(|_| WatchAddErrorKind::UnknownDBError)?;
let watch_id: Julid = query_scalar(ADD_WATCH_QUERY) let watch_id: Julid = query_scalar(ADD_WATCH_QUERY)
.bind(&watch.title) .bind(&watch.title)
.bind(watch.kind) .bind(watch.kind)
@ -162,44 +153,43 @@ async fn add_new_watch_impl(
.bind(&watch.metadata_url) .bind(&watch.metadata_url)
.bind(watch.added_by) .bind(watch.added_by)
.bind(watch.length) .bind(watch.length)
.fetch_one(&mut *tx) .fetch_one(db_pool)
.await .await
.map_err(|err| { .map_err(|err| {
tracing::error!("Got error: {err}"); tracing::error!("Got error: {err}");
WatchAddErrorKind::UnknownDBError AddErrorKind::UnknownDBError
})?; })?;
if let Some(quest) = quest {
query(ADD_WATCH_QUEST_QUERY)
.bind(quest.user)
.bind(watch_id)
.bind(quest.is_public)
.bind(quest.already_watched)
.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
})?;
Ok(watch_id) Ok(watch_id)
} }
/// Add a Watch to your watchlist by selecting it with a checkbox /// Add a Watch to your watchlist by selecting it with a checkbox
pub async fn post_add_watch_quest( pub async fn post_add_watch_quest(
_auth: AuthSession, auth: AuthSession,
State(_pool): State<SqlitePool>, State(pool): State<SqlitePool>,
Form(_form): Form<PostAddExistingWatch>, Form(form): Form<PostAddExistingWatch>,
) -> impl IntoResponse { ) -> Result<impl IntoResponse, AddError> {
todo!() if let Some(user) = auth.user {
let quest = WatchQuest {
user: user.id,
watch: Julid::from_string(&form.watch).unwrap(),
is_public: form.public,
already_watched: form.watched_already,
};
add_watch_quest_impl(&pool, &quest)
.await
.map_err(|_| AddErrorKind::UnknownDBError)?;
let resp = "<style=\"background-color:green\">&#10003;</style>";
Ok(resp.into_response())
} else {
let resp = Redirect::to("/login");
let mut resp = resp.into_response();
resp.headers_mut()
.insert("HX-Redirect", HeaderValue::from_str("/login").unwrap());
Ok(resp)
}
} }
pub async fn _add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Result<(), ()> { pub async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Result<(), ()> {
query(ADD_WATCH_QUEST_QUERY) query(ADD_WATCH_QUEST_QUERY)
.bind(quest.user) .bind(quest.user)
.bind(quest.watch) .bind(quest.watch)

View file

@ -13,7 +13,7 @@
</div> </div>
</div> </div>
{% when None %} <!-- this is for the `when` statement --> {% when None %}
<div class="header_logged_out"> <div class="header_logged_out">
<a href="/login">log in</a> or <a href="/signup">sign up</a>? <a href="/login">log in</a> or <a href="/signup">sign up</a>?
</div> </div>

View file

@ -2,4 +2,13 @@
<td><span class="watchtitle">{{watch.title}}</span></td> <td><span class="watchtitle">{{watch.title}}</span></td>
<td>{{watch.kind}}</td> <td>{{watch.kind}}</td>
<td> {% call m::get_or_default(watch.year(), "when??") -%}</td> <td> {% call m::get_or_default(watch.year(), "when??") -%}</td>
<td>
<form id="add-watch-{{watch.id}}">
<input type="hidden" name="watch" value="{{watch.id}}">
<input type="hidden" name="public" value="true">
<input type="hidden" name="watched_already" value="false">
<button hx-post="/add/watch" hx-target="#add-watch-{{watch.id}}" hx-trigger="click"
hx-swap="OuterHTML">wanna watch?</button>
</form>
</td>
</tr> </tr>