291 lines
8.4 KiB
Rust
291 lines
8.4 KiB
Rust
use axum::{
|
|
extract::{Form, Path, Query, State},
|
|
http::StatusCode,
|
|
response::{IntoResponse, Redirect, Response},
|
|
};
|
|
use serde::Deserialize;
|
|
use sqlx::{query, query_as, SqlitePool};
|
|
|
|
use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage};
|
|
use crate::{
|
|
db_id::DbId,
|
|
util::{empty_string_as_none, year_to_epoch},
|
|
AuthContext, MyWatchesPage, ShowKind, Watch, WatchQuest,
|
|
};
|
|
|
|
//-************************************************************************
|
|
// Constants
|
|
//-************************************************************************
|
|
|
|
const GET_SAVED_WATCHES_QUERY: &str =
|
|
"select * from watches left join witch_watch on $1 = witch_watch.witch and watches.id = witch_watch.watch";
|
|
|
|
const GET_WATCH_QUERY: &str = "select * from watches where id = $1";
|
|
|
|
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,
|
|
search: None,
|
|
};
|
|
|
|
//-************************************************************************
|
|
// Error types for Watch creation
|
|
//-************************************************************************
|
|
|
|
#[Error]
|
|
pub struct WatchAddError(#[from] WatchAddErrorKind);
|
|
|
|
#[Error]
|
|
#[non_exhaustive]
|
|
pub enum WatchAddErrorKind {
|
|
UnknownDBError,
|
|
NotSignedIn,
|
|
}
|
|
|
|
impl IntoResponse for WatchAddError {
|
|
fn into_response(self) -> Response {
|
|
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 forms
|
|
//-************************************************************************
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
|
|
pub struct SearchQuery {
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
pub search: Option<String>,
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
pub title: Option<String>,
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
pub kind: Option<String>,
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
pub year: Option<i64>,
|
|
}
|
|
|
|
// kinda the main form?
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
|
pub struct PostAddNewWatch {
|
|
pub title: String,
|
|
#[serde(default)]
|
|
pub private: bool,
|
|
pub kind: ShowKind,
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
pub year: Option<String>, // need a date-picker or something
|
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
|
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
|
|
//-************************************************************************
|
|
|
|
pub async fn get_add_new_watch(auth: AuthContext) -> impl IntoResponse {
|
|
AddNewWatchPage {
|
|
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<PostAddNewWatch>,
|
|
) -> Result<impl IntoResponse, WatchAddError> {
|
|
if let Some(user) = auth.current_user {
|
|
{
|
|
let watch_id = DbId::new();
|
|
let witch_watch_id = DbId::new();
|
|
let release_date = year_to_epoch(form.year.as_deref());
|
|
let watch = Watch {
|
|
id: watch_id,
|
|
title: form.title,
|
|
kind: form.kind,
|
|
metadata_url: form.metadata_url,
|
|
length: None,
|
|
release_date,
|
|
added_by: user.id,
|
|
};
|
|
let quest = WatchQuest {
|
|
id: witch_watch_id,
|
|
user: user.id,
|
|
watch: watch_id,
|
|
is_public: !form.private,
|
|
already_watched: form.watched_already,
|
|
};
|
|
|
|
add_new_watch_impl(&pool, &watch, Some(quest)).await?;
|
|
|
|
let location = format!("/watch/{watch_id}");
|
|
Ok(Redirect::to(&location))
|
|
}
|
|
} else {
|
|
Err(WatchAddErrorKind::NotSignedIn.into())
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn add_new_watch_impl(
|
|
db_pool: &SqlitePool,
|
|
watch: &Watch,
|
|
quest: Option<WatchQuest>,
|
|
) -> Result<(), WatchAddError> {
|
|
let mut tx = db_pool
|
|
.begin()
|
|
.await
|
|
.map_err(|_| WatchAddErrorKind::UnknownDBError)?;
|
|
query(ADD_WATCH_QUERY)
|
|
.bind(watch.id)
|
|
.bind(&watch.title)
|
|
.bind(watch.kind)
|
|
.bind(watch.release_date)
|
|
.bind(&watch.metadata_url)
|
|
.bind(watch.added_by)
|
|
.execute(&mut tx)
|
|
.await
|
|
.map_err(|err| {
|
|
tracing::error!("Got error: {err}");
|
|
WatchAddErrorKind::UnknownDBError
|
|
})?;
|
|
|
|
if let Some(quest) = quest {
|
|
query(ADD_WITCH_WATCH_QUERY)
|
|
.bind(quest.id)
|
|
.bind(quest.user)
|
|
.bind(quest.watch)
|
|
.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(())
|
|
}
|
|
|
|
/// Add a Watch to your watchlist by selecting it with a checkbox
|
|
pub async fn post_add_watch_quest(
|
|
_auth: AuthContext,
|
|
State(_pool): State<SqlitePool>,
|
|
Form(_form): Form<PostAddExistingWatch>,
|
|
) -> impl IntoResponse {
|
|
todo!()
|
|
}
|
|
|
|
pub async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Result<(), ()> {
|
|
query(ADD_WITCH_WATCH_QUERY)
|
|
.bind(quest.id)
|
|
.bind(quest.user)
|
|
.bind(quest.watch)
|
|
.bind(quest.is_public)
|
|
.bind(quest.already_watched)
|
|
.execute(pool)
|
|
.await
|
|
.map_err(|err| {
|
|
tracing::error!("Got error: {err}");
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
/// A single Watch
|
|
pub async fn get_watch(
|
|
auth: AuthContext,
|
|
watch: Option<Path<String>>,
|
|
State(pool): State<SqlitePool>,
|
|
) -> impl IntoResponse {
|
|
let id = if let Some(Path(id)) = watch {
|
|
id
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
let id = id.trim();
|
|
let id = DbId::from_string(id).unwrap_or_default();
|
|
let q = query_as(GET_WATCH_QUERY).bind(id);
|
|
let watch: Option<Watch> = q.fetch_one(&pool).await.ok();
|
|
|
|
GetWatchPage {
|
|
watch,
|
|
user: auth.current_user,
|
|
}
|
|
}
|
|
|
|
/// everything the user has saved
|
|
pub async fn get_watches(auth: AuthContext, State(pool): State<SqlitePool>) -> impl IntoResponse {
|
|
let user = auth.current_user;
|
|
let watches: Vec<Watch> = if (user).is_some() {
|
|
query_as(GET_SAVED_WATCHES_QUERY)
|
|
.bind(user.as_ref().unwrap().id)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.unwrap_or_default()
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
MyWatchesPage { watches, user }
|
|
}
|
|
|
|
pub async fn get_search_watch(
|
|
auth: AuthContext,
|
|
State(pool): State<SqlitePool>,
|
|
search: Query<SearchQuery>,
|
|
) -> impl IntoResponse {
|
|
let user = auth.current_user;
|
|
|
|
let (search_string, qstring) = if search.0 != EMPTY_SEARCH_QUERY_STRUCT {
|
|
let s = search.0;
|
|
let q = if let Some(title) = &s.title {
|
|
title
|
|
} else if let Some(search) = &s.search {
|
|
search
|
|
} else {
|
|
""
|
|
};
|
|
(format!("{s:?}"), format!("%{}%", q.trim()))
|
|
} else {
|
|
("".to_string(), "%".to_string())
|
|
};
|
|
|
|
// until tantivy search
|
|
let watches: Vec<Watch> = query_as("select * from watches where title like ?")
|
|
.bind(&qstring)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
SearchWatchesPage {
|
|
watches,
|
|
user,
|
|
search: search_string,
|
|
}
|
|
}
|