diff options
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | base/src/database.rs | 30 | ||||
-rw-r--r-- | client/src/lib.rs | 12 | ||||
-rw-r--r-- | common/Cargo.toml | 1 | ||||
-rw-r--r-- | common/src/impl.rs | 74 | ||||
-rw-r--r-- | common/src/lib.rs | 6 | ||||
-rw-r--r-- | server/src/routes/compat/youtube.rs | 1 | ||||
-rw-r--r-- | server/src/routes/stream.rs | 2 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 31 | ||||
-rw-r--r-- | server/src/routes/ui/layout.rs | 1 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 35 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 18 | ||||
-rw-r--r-- | server/src/routes/userdata.rs | 16 |
13 files changed, 167 insertions, 67 deletions
@@ -1211,6 +1211,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1739,6 +1745,7 @@ dependencies = [ "bincode", "blake3", "chrono", + "hex", "rocket", "serde", ] diff --git a/base/src/database.rs b/base/src/database.rs index f1e023c..c0699f0 100644 --- a/base/src/database.rs +++ b/base/src/database.rs @@ -15,6 +15,7 @@ use std::{ fs::create_dir_all, path::Path, sync::{Arc, RwLock}, + time::SystemTime, }; use tantivy::{ collector::{Count, TopDocs}, @@ -35,6 +36,7 @@ const T_NODE_CHILDREN: TableDefinition<([u8; 32], [u8; 32]), ()> = const T_NODE_EXTERNAL_ID: TableDefinition<(&str, &str), [u8; 32]> = TableDefinition::new("node_external_id"); const T_IMPORT_FILE_MTIME: TableDefinition<&[u8], u64> = TableDefinition::new("import_file_mtime"); +const T_NODE_MTIME: TableDefinition<[u8; 32], u64> = TableDefinition::new("node_mtime"); #[derive(Clone)] pub struct Database { @@ -61,6 +63,7 @@ impl Database { txn.open_table(T_USER)?; txn.open_table(T_USER_NODE)?; txn.open_table(T_NODE)?; + txn.open_table(T_NODE_MTIME)?; txn.open_table(T_NODE_CHILDREN)?; txn.open_table(T_NODE_EXTERNAL_ID)?; txn.open_table(T_IMPORT_FILE_MTIME)?; @@ -101,19 +104,32 @@ impl Database { .map(|r| r.map(|r| NodeID(r.0.value().1))) .collect::<Result<Vec<_>, StorageError>>()?) } + pub fn get_nodes_modified_since(&self, since: u64) -> Result<Vec<NodeID>> { + let txn = self.inner.begin_read()?; + let t_node_mtime = txn.open_table(T_NODE_MTIME)?; + Ok(t_node_mtime + .iter()? + .flat_map(|r| r.map(|r| (NodeID(r.0.value()), r.1.value()))) + .filter(|(_, mtime)| *mtime >= since) + .map(|(id, _)| id) + .collect()) + } pub fn clear_nodes(&self) -> Result<()> { let mut txn = self.inner.begin_write()?; let mut t_node = txn.open_table(T_NODE)?; + let mut t_node_mtime = txn.open_table(T_NODE_MTIME)?; let mut t_node_children = txn.open_table(T_NODE_CHILDREN)?; let mut t_node_external_id = txn.open_table(T_NODE_EXTERNAL_ID)?; let mut t_import_file_mtime = txn.open_table(T_IMPORT_FILE_MTIME)?; t_node.retain(|_, _| false)?; + t_node_mtime.retain(|_, _| false)?; t_node_children.retain(|_, _| false)?; t_node_external_id.retain(|_, _| false)?; t_import_file_mtime.retain(|_, _| false)?; drop(( t_node, + t_node_mtime, t_node_children, t_node_external_id, t_import_file_mtime, @@ -138,11 +154,16 @@ impl Database { id: NodeID, update: impl FnOnce(&mut Node) -> Result<()>, ) -> Result<()> { + let time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); let mut txn = self.inner.begin_write()?; - let mut t_nodes = txn.open_table(T_NODE)?; + let mut t_node = txn.open_table(T_NODE)?; + let mut t_node_mtime = txn.open_table(T_NODE_MTIME)?; let mut t_node_children = txn.open_table(T_NODE_CHILDREN)?; let mut t_node_external_id = txn.open_table(T_NODE_EXTERNAL_ID)?; - let mut node = t_nodes.get(id.0)?.map(|v| v.value().0).unwrap_or_default(); + let mut node = t_node.get(id.0)?.map(|v| v.value().0).unwrap_or_default(); update(&mut node)?; for parent in &node.parents { t_node_children.insert((parent.0, id.0), ())?; @@ -150,8 +171,9 @@ impl Database { for (pl, eid) in &node.external_ids { t_node_external_id.insert((pl.as_str(), eid.as_str()), id.0)?; } - t_nodes.insert(&id.0, Ser(node))?; - drop((t_nodes, t_node_children, t_node_external_id)); + t_node.insert(&id.0, Ser(node))?; + t_node_mtime.insert(&id.0, time)?; + drop((t_node, t_node_mtime, t_node_children, t_node_external_id)); txn.set_durability(Durability::Eventual); txn.commit()?; Ok(()) diff --git a/client/src/lib.rs b/client/src/lib.rs index 5da718d..e0ab440 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -71,8 +71,8 @@ impl Session { format!("session={}", self.session_token) } - pub async fn node(&self, id: &str) -> Result<Node> { - debug!("downloading node {id:?}"); + pub async fn node(&self, id: NodeIDOrSlug) -> Result<Node> { + debug!("downloading node {id}"); Ok(self .client .get(format!("{}/n/{id}", self.instance.base())) @@ -86,11 +86,11 @@ impl Session { pub async fn node_thumbnail( &self, writer: impl UnpinWrite, - id: &str, + id: NodeIDOrSlug, width: usize, time: f64, ) -> Result<()> { - debug!("downloading thumbnail for {id:?} at {time}s"); + debug!("downloading thumbnail for {id} at {time}s"); self.download_url( writer, format!( @@ -113,14 +113,14 @@ impl Session { pub async fn stream( &self, writer: impl UnpinWrite, - id: &str, + id: NodeIDOrSlug, stream_spec: &StreamSpec, ) -> Result<()> { self.download_url(writer, self.stream_url(id, stream_spec)) .await } - pub fn stream_url(&self, id: &str, stream_spec: &StreamSpec) -> String { + pub fn stream_url(&self, id: NodeIDOrSlug, stream_spec: &StreamSpec) -> String { format!( "{}/n/{}/stream?{}&{}", self.instance.base(), diff --git a/common/Cargo.toml b/common/Cargo.toml index 9038bc4..775cd2a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,6 +9,7 @@ bincode = { version = "2.0.0-rc.3", features = ["derive"] } rocket = { workspace = true, optional = true } chrono = { version = "0.4.39", features = ["serde"] } blake3 = "1.5.5" +hex = "0.4.3" [features] rocket = ["dep:rocket"] diff --git a/common/src/impl.rs b/common/src/impl.rs index 3814b1d..db702d3 100644 --- a/common/src/impl.rs +++ b/common/src/impl.rs @@ -4,8 +4,10 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ use crate::{ - Node, NodeID, ObjectIds, PeopleGroup, SourceTrack, SourceTrackKind, TmdbKind, TraktKind, + Node, NodeID, NodeIDOrSlug, ObjectIds, PeopleGroup, SourceTrack, SourceTrackKind, TmdbKind, + TraktKind, }; +use hex::FromHexError; use std::{fmt::Display, str::FromStr}; impl SourceTrackKind { @@ -170,3 +172,73 @@ impl NodeID { pub const MIN: NodeID = NodeID([0; 32]); pub const MAX: NodeID = NodeID([255; 32]); } + +#[cfg(feature = "rocket")] +impl<'a> rocket::request::FromParam<'a> for NodeID { + type Error = FromHexError; + fn from_param(param: &'a str) -> Result<Self, Self::Error> { + if let Some(id) = param.strip_prefix("+") { + let mut k = [0; 32]; + hex::decode_to_slice(id, &mut k)?; + Ok(NodeID(k)) + } else { + Ok(NodeID::from_slug(¶m)) + } + } +} +#[cfg(feature = "rocket")] +impl rocket::http::uri::fmt::FromUriParam<rocket::http::uri::fmt::Path, NodeID> for NodeID { + type Target = NodeID; + fn from_uri_param(param: NodeID) -> Self::Target { + param + } +} +#[cfg(feature = "rocket")] +impl<'a> rocket::http::uri::fmt::FromUriParam<rocket::http::uri::fmt::Path, &'a String> for NodeID { + type Target = &'a str; + fn from_uri_param(param: &'a String) -> Self::Target { + param.as_str() + } +} +#[cfg(feature = "rocket")] +impl<'a> rocket::http::uri::fmt::FromUriParam<rocket::http::uri::fmt::Path, &'a str> for NodeID { + type Target = &'a str; + fn from_uri_param(param: &'a str) -> Self::Target { + param + } +} +#[cfg(feature = "rocket")] +impl rocket::http::uri::fmt::UriDisplay<rocket::http::uri::fmt::Path> for NodeID { + fn fmt( + &self, + f: &mut rocket::http::uri::fmt::Formatter<'_, rocket::http::uri::fmt::Path>, + ) -> std::fmt::Result { + f.write_value(format!("+{}", hex::encode(self.0)))?; + Ok(()) + } +} +impl Display for NodeID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("+")?; + f.write_str(&hex::encode(self.0))?; + Ok(()) + } +} +impl Display for NodeIDOrSlug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NodeIDOrSlug::ID(x) => x.fmt(f), + NodeIDOrSlug::Slug(x) => x.fmt(f), + } + } +} +impl From<NodeID> for NodeIDOrSlug { + fn from(value: NodeID) -> Self { + Self::ID(value) + } +} +impl From<String> for NodeIDOrSlug { + fn from(value: String) -> Self { + Self::Slug(value) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 74476fe..43adfba 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -3,6 +3,7 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ +#![feature(array_try_map)] pub mod api; pub mod config; pub mod helpers; @@ -26,6 +27,11 @@ use std::{ )] pub struct NodeID(pub [u8; 32]); +pub enum NodeIDOrSlug { + ID(NodeID), + Slug(String), +} + #[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)] pub struct Node { #[serde(default)] diff --git a/server/src/routes/compat/youtube.rs b/server/src/routes/compat/youtube.rs index 5880044..78eee8a 100644 --- a/server/src/routes/compat/youtube.rs +++ b/server/src/routes/compat/youtube.rs @@ -11,6 +11,7 @@ use crate::routes::ui::{ }; use anyhow::anyhow; use jellybase::database::Database; +use jellycommon::NodeID; use rocket::{get, response::Redirect, State}; #[get("/watch?<v>")] diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs index 401faba..1fb136c 100644 --- a/server/src/routes/stream.rs +++ b/server/src/routes/stream.rs @@ -95,7 +95,7 @@ pub async fn r_stream( .await?; let uri = session.stream_url( - &node.slug, + node.slug.clone().into(), &StreamSpec { track: vec![remote_index], ..spec diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index bd48f35..a925e31 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -9,7 +9,7 @@ use base64::Engine; use jellybase::{ assetfed::AssetInner, cache::async_cache_file, database::Database, federation::Federation, CONF, }; -use jellycommon::{LocalTrack, PeopleGroup, SourceTrackKind, TrackSource}; +use jellycommon::{LocalTrack, NodeID, PeopleGroup, SourceTrackKind, TrackSource}; use log::info; use rocket::{get, http::ContentType, response::Redirect, State}; use std::{path::PathBuf, str::FromStr}; @@ -64,13 +64,11 @@ pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> { pub async fn r_item_poster( _session: Session, db: &State<Database>, - id: &str, + id: NodeID, width: Option<usize>, ) -> MyResult<Redirect> { // TODO perm - let node = db - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; + let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; let mut asset = node.poster.clone(); if asset.is_none() { @@ -84,17 +82,16 @@ pub async fn r_item_poster( }); Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) } + #[get("/n/<id>/backdrop?<width>")] pub async fn r_item_backdrop( _session: Session, db: &State<Database>, - id: &str, + id: NodeID, width: Option<usize>, ) -> MyResult<Redirect> { // TODO perm - let node = db - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; + let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; let mut asset = node.backdrop.clone(); if asset.is_none() { @@ -113,16 +110,14 @@ pub async fn r_item_backdrop( pub async fn r_person_asset( _session: Session, db: &State<Database>, - id: &str, + id: NodeID, index: usize, group: String, width: Option<usize>, ) -> MyResult<Redirect> { // TODO perm - let node = db - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; + let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; let app = node .people .get(&PeopleGroup::from_str(&group).map_err(|()| anyhow!("unknown people group"))?) @@ -146,13 +141,11 @@ pub async fn r_node_thumbnail( _session: Session, db: &State<Database>, fed: &State<Federation>, - id: &str, + id: NodeID, t: f64, width: Option<usize>, ) -> MyResult<Redirect> { - let node = db - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; + let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; let media = node.media.as_ref().ok_or(anyhow!("no media"))?; let (thumb_track_index, thumb_track) = media @@ -190,8 +183,8 @@ pub async fn r_node_thumbnail( ) .await?; - async_cache_file(&["fed-thumb", id, &format!("{t}")], |out| { - session.node_thumbnail(out, id, 2048, t) + async_cache_file(&["fed-thumb", &format!("{id} {t}")], |out| { + session.node_thumbnail(out, id.into(), 2048, t) }) .await? } diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs index eb22854..3b2db28 100644 --- a/server/src/routes/ui/layout.rs +++ b/server/src/routes/ui/layout.rs @@ -20,6 +20,7 @@ use crate::{ use futures::executor::block_on; use jellybase::CONF; use jellycommon::user::Theme; +use jellycommon::NodeID; use jellyimport::is_importing; use markup::{DynRender, Render}; use rocket::{ diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs index 365182c..ebd21db 100644 --- a/server/src/routes/ui/node.rs +++ b/server/src/routes/ui/node.rs @@ -40,21 +40,20 @@ use std::sync::Arc; /// This function is a stub and only useful for use in the uri! macro. #[get("/n/<id>")] -pub fn r_library_node(id: String) { - drop(id) +pub fn r_library_node(id: NodeID) { + id.0[0]; } -#[get("/n/<slug>?<parents>&<children>&<filter..>")] +#[get("/n/<id>?<parents>&<children>&<filter..>")] pub async fn r_library_node_filter<'a>( session: Session, - slug: &'a str, + id: NodeID, db: &'a State<Database>, aj: AcceptJson, filter: NodeFilterSort, parents: bool, children: bool, ) -> MyResult<Either<DynLayoutPage<'a>, Json<ApiNodeResponse>>> { - let id = NodeID::from_slug(slug); let (node, udata) = db.get_node_with_userdata(id, &session)?; let mut children = if !*aj || children { @@ -99,7 +98,7 @@ pub async fn r_library_node_filter<'a>( Either::Left(LayoutPage { title: node.title.clone().unwrap_or_default(), content: markup::new! { - @NodePage { node: &node, id: slug, udata: &udata, children: &children, parents: &parents, filter: &filter } + @NodePage { node: &node, udata: &udata, children: &children, parents: &parents, filter: &filter } }, ..Default::default() }) @@ -152,43 +151,43 @@ markup::define! { } } } - NodePage<'a>(id: &'a str, node: &'a Node, udata: &'a NodeUserData, children: &'a [(Arc<Node>, NodeUserData)], parents: &'a [(Arc<Node>, NodeUserData)], filter: &'a NodeFilterSort) { + NodePage<'a>(node: &'a Node, udata: &'a NodeUserData, children: &'a [(Arc<Node>, NodeUserData)], parents: &'a [(Arc<Node>, NodeUserData)], filter: &'a NodeFilterSort) { @if !matches!(node.kind, NodeKind::Collection) { - img.backdrop[src=uri!(r_item_backdrop(id, Some(2048))), loading="lazy"]; + img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))), loading="lazy"]; } .page.node { @if !matches!(node.kind, NodeKind::Collection) { @let cls = format!("bigposter {}", aspect_class(node.kind)); - div[class=cls] { img[src=uri!(r_item_poster(id, Some(2048))), loading="lazy"]; } + div[class=cls] { img[src=uri!(r_item_poster(&node.slug, Some(2048))), loading="lazy"]; } } .title { h1 { @node.title } ul.parents { @for (node, _) in *parents { li { a.component[href=uri!(r_library_node(&node.slug))] { @node.title } }}} - @if node.media.is_some() { a.play[href=&uri!(r_player(id, PlayerConfig::default()))] { "Watch now" }} + @if node.media.is_some() { a.play[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "Watch now" }} @if !matches!(node.kind, NodeKind::Collection | NodeKind::Channel) { @if matches!(udata.watched, WatchedState::None | WatchedState::Pending | WatchedState::Progress(_)) { - form.mark_watched[method="POST", action=uri!(r_node_userdata_watched(id, UrlWatchedState::Watched))] { + form.mark_watched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Watched))] { input[type="submit", value="Mark Watched"]; } } @if matches!(udata.watched, WatchedState::Watched) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(id, UrlWatchedState::None))] { + form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] { input[type="submit", value="Mark Unwatched"]; } } @if matches!(udata.watched, WatchedState::None) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(id, UrlWatchedState::Pending))] { + form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::Pending))] { input[type="submit", value="Add to Watchlist"]; } } @if matches!(udata.watched, WatchedState::Pending) { - form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(id, UrlWatchedState::None))] { + form.mark_unwatched[method="POST", action=uri!(r_node_userdata_watched(&node.slug, UrlWatchedState::None))] { input[type="submit", value="Remove from Watchlist"]; } } - form.rating[method="POST", action=uri!(r_node_userdata_rating(id))] { + form.rating[method="POST", action=uri!(r_node_userdata_rating(&node.slug))] { input[type="range", name="rating", min=-10, max=10, step=1, value=udata.rating]; input[type="submit", value="Update Rating"]; } @@ -207,8 +206,8 @@ markup::define! { @let (inl, sub) = format_chapter(chap); li { .card."aspect-thumb" { .poster { - a[href=&uri!(r_player(id, PlayerConfig::seek(chap.time_start.unwrap_or(0.))))] { - img[src=&uri!(r_node_thumbnail(id, chapter_key_time(chap, media.duration), Some(1024))), loading="lazy"]; + a[href=&uri!(r_player(&node.slug, PlayerConfig::seek(chap.time_start.unwrap_or(0.))))] { + img[src=&uri!(r_node_thumbnail(&node.slug, chapter_key_time(chap, media.duration), Some(1024))), loading="lazy"]; } .cardhover { .props { p { @inl } } } } @@ -225,7 +224,7 @@ markup::define! { li { .card."aspect-port" { .poster { a[href="#"] { - img[src=&uri!(r_person_asset(id, i, group.to_string(), Some(1024))), loading="lazy"]; + img[src=&uri!(r_person_asset(&node.slug, i, group.to_string(), Some(1024))), loading="lazy"]; } } .title { diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs index b24e5e9..f680a45 100644 --- a/server/src/routes/ui/player.rs +++ b/server/src/routes/ui/player.rs @@ -20,7 +20,7 @@ use jellybase::{permission::PermissionSetExt, CONF}; use jellycommon::{ stream::{StreamFormat, StreamSpec}, user::{PermissionSet, PlayerKind, UserPermission}, - Node, SourceTrackKind, TrackID, + Node, NodeID, SourceTrackKind, TrackID, }; use markup::DynRender; use rocket::{get, response::Redirect, Either, FromForm, State, UriDisplayQuery}; @@ -61,12 +61,10 @@ fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: & pub fn r_player<'a>( sess: Session, db: &'a State<Database>, - id: &'a str, + id: NodeID, conf: PlayerConfig, ) -> MyResult<Either<DynLayoutPage<'a>, Redirect>> { - let item = db - .get_node_slug(id)? - .ok_or(anyhow!("node does not exist"))?; + let node = db.get_node(id)?.ok_or(anyhow!("node does not exist"))?; let native_session = |action: &str| { let perm = [ @@ -81,7 +79,7 @@ pub fn r_player<'a>( action, conf.t.unwrap_or(0.), &sess.user.native_secret, - id, + &id.to_string(), &token::create( sess.user.name, PermissionSet(perm.map(|e| (e, true)).into()), @@ -114,15 +112,15 @@ pub fn r_player<'a>( let playing = !spec.track.is_empty(); - let conf = player_conf(item.clone(), playing)?; + let conf = player_conf(node.clone(), playing)?; Ok(Either::Left(LayoutPage { - title: item.title.to_owned().unwrap_or_default(), + title: node.title.to_owned().unwrap_or_default(), class: Some("player"), content: markup::new! { @if playing { - video[src=uri!(r_stream(&id, &spec)), controls, preload="auto"]{} + video[src=uri!(r_stream(&node.slug, &spec)), controls, preload="auto"]{} } else { - img.backdrop[src=uri!(r_item_backdrop(&id, Some(2048))).to_string()]; + img.backdrop[src=uri!(r_item_backdrop(&node.slug, Some(2048))).to_string()]; } @conf }, diff --git a/server/src/routes/userdata.rs b/server/src/routes/userdata.rs index 6fcd7a0..01776da 100644 --- a/server/src/routes/userdata.rs +++ b/server/src/routes/userdata.rs @@ -26,10 +26,10 @@ pub enum UrlWatchedState { pub fn r_node_userdata( session: Session, db: &State<Database>, - id: &str, + id: NodeID, ) -> MyResult<Json<NodeUserData>> { let u = db - .get_node_udata(NodeID::from_slug(id), &session.user.name)? + .get_node_udata(id, &session.user.name)? .unwrap_or_default(); Ok(Json(u)) } @@ -38,11 +38,11 @@ pub fn r_node_userdata( pub async fn r_node_userdata_watched( session: Session, db: &State<Database>, - id: &str, + id: NodeID, state: UrlWatchedState, ) -> MyResult<Redirect> { // TODO perm - db.update_node_udata(NodeID::from_slug(id), &session.user.name, |udata| { + db.update_node_udata(id, &session.user.name, |udata| { udata.watched = match state { UrlWatchedState::None => WatchedState::None, UrlWatchedState::Watched => WatchedState::Watched, @@ -63,11 +63,11 @@ pub struct UpdateRating { pub async fn r_node_userdata_rating( session: Session, db: &State<Database>, - id: &str, + id: NodeID, form: Form<UpdateRating>, ) -> MyResult<Redirect> { // TODO perm - db.update_node_udata(NodeID::from_slug(id), &session.user.name, |udata| { + db.update_node_udata(id, &session.user.name, |udata| { udata.rating = form.rating; Ok(()) })?; @@ -78,11 +78,11 @@ pub async fn r_node_userdata_rating( pub async fn r_node_userdata_progress( session: Session, db: &State<Database>, - id: &str, + id: NodeID, t: f64, ) -> MyResult<()> { // TODO perm - db.update_node_udata(NodeID::from_slug(id), &session.user.name, |udata| { + db.update_node_udata(id, &session.user.name, |udata| { udata.watched = match udata.watched { WatchedState::None | WatchedState::Pending | WatchedState::Progress(_) => { WatchedState::Progress(t) |