aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-07 14:08:20 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-07 14:08:20 +0100
commit346095d20e3d817d150cbea49e87a49fbcaa2304 (patch)
tree1fc3868fa68287e916e511c8f5b43b62087f0ff9
parent976bdd8e2d14049c766a654a7575f9f5109c7395 (diff)
downloadjellything-346095d20e3d817d150cbea49e87a49fbcaa2304.tar
jellything-346095d20e3d817d150cbea49e87a49fbcaa2304.tar.bz2
jellything-346095d20e3d817d150cbea49e87a49fbcaa2304.tar.zst
nodeid guard
-rw-r--r--Cargo.lock7
-rw-r--r--base/src/database.rs30
-rw-r--r--client/src/lib.rs12
-rw-r--r--common/Cargo.toml1
-rw-r--r--common/src/impl.rs74
-rw-r--r--common/src/lib.rs6
-rw-r--r--server/src/routes/compat/youtube.rs1
-rw-r--r--server/src/routes/stream.rs2
-rw-r--r--server/src/routes/ui/assets.rs31
-rw-r--r--server/src/routes/ui/layout.rs1
-rw-r--r--server/src/routes/ui/node.rs35
-rw-r--r--server/src/routes/ui/player.rs18
-rw-r--r--server/src/routes/userdata.rs16
13 files changed, 167 insertions, 67 deletions
diff --git a/Cargo.lock b/Cargo.lock
index bc90711..d1985f6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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(&param))
+ }
+ }
+}
+#[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)