Make it easier to add watches from a CLI tool.

This commit is contained in:
Joe Ardent 2023-07-02 15:16:47 -07:00
parent 04549fd7a5
commit 83da336a3f
4 changed files with 104 additions and 48 deletions

View file

@ -10,6 +10,9 @@ pub use db::get_db_pool;
pub use db_id::DbId; pub use db_id::DbId;
pub mod import_utils; pub mod import_utils;
pub use users::User;
pub use watches::{ShowKind, Watch, WatchQuest};
// everything else is private to the crate // everything else is private to the crate
mod db; mod db;
mod db_id; mod db_id;
@ -24,10 +27,7 @@ mod watches;
// things we want in the crate namespace // things we want in the crate namespace
use optional_optional_user::OptionalOptionalUser; use optional_optional_user::OptionalOptionalUser;
use templates::*; use templates::*;
use users::User; use watches::templates::*;
use watches::{templates::*, ShowKind, Watch};
use crate::watches::handlers::get_watch;
type AuthContext = axum_login::extractors::AuthContext<DbId, User, axum_login::SqliteStore<User>>; type AuthContext = axum_login::extractors::AuthContext<DbId, User, axum_login::SqliteStore<User>>;
@ -42,7 +42,7 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
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, get_signup_success, post_create_user}; use signup::{get_create_user, get_signup_success, post_create_user};
use watches::handlers::{ use watches::handlers::{
get_add_new_watch, get_search_watch, get_watches, post_add_existing_watch, get_add_new_watch, get_search_watch, get_watch, get_watches, post_add_existing_watch,
post_add_new_watch, post_add_new_watch,
}; };

View file

@ -20,7 +20,7 @@ pub fn validate_optional_length<E: Error>(
} }
} }
/// Serde deserialization decorator to map empty Strings to None, /// Serde deserialization decorator to map empty Strings to None
pub fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error> pub fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
@ -35,3 +35,17 @@ where
.map(Some), .map(Some),
} }
} }
/// Convert a stringy number like "1999" to a 64-bit signed unix epoch-based
/// timestamp
pub fn year_to_epoch(year: Option<&str>) -> Option<i64> {
year?
.trim()
.parse::<i32>()
.map(|year| {
let years = (year - 1970) as f32;
let days = (years * 365.2425) as i64;
days * 24 * 60 * 60
})
.ok()
}

View file

@ -7,7 +7,11 @@ use serde::Deserialize;
use sqlx::{query, query_as, SqlitePool}; use sqlx::{query, query_as, SqlitePool};
use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage}; use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage};
use crate::{db_id::DbId, util::empty_string_as_none, AuthContext, MyWatchesPage, ShowKind, Watch}; use crate::{
db_id::DbId,
util::{empty_string_as_none, year_to_epoch},
AuthContext, MyWatchesPage, ShowKind, Watch, WatchQuest,
};
//-************************************************************************ //-************************************************************************
// Constants // Constants
@ -116,48 +120,26 @@ pub async fn post_add_new_watch(
{ {
let watch_id = DbId::new(); let watch_id = DbId::new();
let witch_watch_id = DbId::new(); let witch_watch_id = DbId::new();
let release_date = form.year.map(|year| match year.trim().parse::<i32>() { let release_date = year_to_epoch(form.year.as_deref());
Ok(year) => { let watch = Watch {
let years = (year - 1970) as i64; id: watch_id,
let days = (years as f32 * 365.2425) as i64; title: form.title,
Some(days * 24 * 60 * 60) kind: form.kind,
} metadata_url: form.metadata_url,
Err(_) => None, length: None,
}); release_date,
let mut tx = pool added_by: user.id,
.begin() };
.await let quest = WatchQuest {
.map_err(|_| WatchAddErrorKind::UnknownDBError)?; id: witch_watch_id,
query(ADD_WATCH_QUERY) user: user.id,
.bind(watch_id) watch: watch_id,
.bind(&form.title) is_public: !form.private,
.bind(form.kind) already_watched: form.watched_already,
.bind(release_date) };
.bind(form.metadata_url)
.bind(user.id) add_new_watch_impl(&pool, &watch, Some(quest)).await?;
.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 location = format!("/watch/{watch_id}"); let location = format!("/watch/{watch_id}");
Ok(Redirect::to(&location)) Ok(Redirect::to(&location))
} }
@ -166,6 +148,51 @@ pub async fn post_add_new_watch(
} }
} }
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 /// Add a Watch to your watchlist by selecting it with a checkbox
pub async fn post_add_existing_watch( pub async fn post_add_existing_watch(
_auth: AuthContext, _auth: AuthContext,

View file

@ -52,6 +52,9 @@ impl From<i64> for ShowKind {
} }
} }
//-************************************************************************
/// Something able to be watched.
//-************************************************************************
#[derive( #[derive(
Debug, Debug,
Default, Default,
@ -86,3 +89,15 @@ impl Watch {
} }
} }
} }
//-************************************************************************
/// Something a user wants to watch
//-************************************************************************
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WatchQuest {
pub id: DbId,
pub user: DbId,
pub watch: DbId,
pub is_public: bool,
pub already_watched: bool,
}