aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/routes/ui')
-rw-r--r--server/src/routes/ui/account/mod.rs54
-rw-r--r--server/src/routes/ui/account/session/guard.rs17
-rw-r--r--server/src/routes/ui/account/settings.rs66
-rw-r--r--server/src/routes/ui/admin/mod.rs170
-rw-r--r--server/src/routes/ui/admin/user.rs67
-rw-r--r--server/src/routes/ui/assets.rs71
-rw-r--r--server/src/routes/ui/browser.rs30
-rw-r--r--server/src/routes/ui/error.rs42
-rw-r--r--server/src/routes/ui/home.rs60
-rw-r--r--server/src/routes/ui/node.rs62
-rw-r--r--server/src/routes/ui/player.rs17
-rw-r--r--server/src/routes/ui/search.rs58
-rw-r--r--server/src/routes/ui/sort.rs32
13 files changed, 254 insertions, 492 deletions
diff --git a/server/src/routes/ui/account/mod.rs b/server/src/routes/ui/account/mod.rs
index d73cf4c..6139a08 100644
--- a/server/src/routes/ui/account/mod.rs
+++ b/server/src/routes/ui/account/mod.rs
@@ -8,7 +8,7 @@ pub mod settings;
use super::{error::MyError, layout::LayoutPage};
use crate::{
- database::DataAcid,
+ database::Database,
routes::ui::{
account::session::Session, error::MyResult, home::rocket_uri_macro_r_home,
layout::DynLayoutPage,
@@ -18,10 +18,7 @@ use crate::{
use anyhow::anyhow;
use argon2::{password_hash::Salt, Argon2, PasswordHasher};
use chrono::Duration;
-use jellybase::{
- database::{Ser, TableExt, T_INVITE, T_USER},
- CONF,
-};
+use jellybase::CONF;
use jellycommon::user::{User, UserPermission};
use rocket::{
form::{Contextual, Form},
@@ -124,7 +121,7 @@ pub fn r_account_logout() -> DynLayoutPage<'static> {
#[post("/account/register", data = "<form>")]
pub fn r_account_register_post<'a>(
- database: &'a State<DataAcid>,
+ database: &'a State<Database>,
_sess: Option<Session>,
form: Form<Contextual<'a, RegisterForm>>,
) -> MyResult<DynLayoutPage<'a>> {
@@ -134,31 +131,16 @@ pub fn r_account_register_post<'a>(
None => return Err(format_form_error(form)),
};
- let txn = database.begin_write()?;
- let mut invites = txn.open_table(T_INVITE)?;
- let mut users = txn.open_table(T_USER)?;
-
- if invites.remove(&*form.invitation)?.is_none() {
- Err(anyhow!("invitation invalid"))?;
- }
- let prev_user = users
- .insert(
- &*form.username,
- Ser(User {
- display_name: form.username.clone(),
- name: form.username.clone(),
- password: hash_password(&form.username, &form.password),
- ..Default::default()
- }),
- )?
- .map(|x| x.value().0);
- if prev_user.is_some() {
- Err(anyhow!("username taken"))?;
- }
-
- drop(users);
- drop(invites);
- txn.commit()?;
+ database.register_user(
+ &form.invitation,
+ &form.username,
+ User {
+ display_name: form.username.clone(),
+ name: form.username.clone(),
+ password: hash_password(&form.username, &form.password),
+ ..Default::default()
+ },
+ )?;
Ok(LayoutPage {
title: "Registration successful".to_string(),
@@ -175,7 +157,7 @@ pub fn r_account_register_post<'a>(
#[post("/account/login", data = "<form>")]
pub fn r_account_login_post(
- database: &State<DataAcid>,
+ database: &State<Database>,
jar: &CookieJar,
form: Form<Contextual<LoginForm>>,
) -> MyResult<Redirect> {
@@ -202,17 +184,17 @@ pub fn r_account_logout_post(jar: &CookieJar) -> MyResult<Redirect> {
}
pub fn login_logic(
- database: &DataAcid,
+ database: &Database,
username: &str,
password: &str,
expire: Option<i64>,
drop_permissions: Option<HashSet<UserPermission>>,
) -> MyResult<String> {
- // hashing the password regardless if the accounts exists to prevent timing attacks
+ // hashing the password regardless if the accounts exists to better resist timing attacks
let password = hash_password(username, password);
- let mut user = T_USER
- .get(database, username)?
+ let mut user = database
+ .get_user(username)?
.ok_or(anyhow!("invalid password"))?;
if user.password != password {
diff --git a/server/src/routes/ui/account/session/guard.rs b/server/src/routes/ui/account/session/guard.rs
index 6a9bdaf..57540cf 100644
--- a/server/src/routes/ui/account/session/guard.rs
+++ b/server/src/routes/ui/account/session/guard.rs
@@ -4,9 +4,8 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use super::{AdminSession, Session};
-use crate::{database::DataAcid, routes::ui::error::MyError};
+use crate::{database::Database, routes::ui::error::MyError};
use anyhow::anyhow;
-use jellybase::database::T_USER;
use log::warn;
use rocket::{
async_trait,
@@ -36,19 +35,9 @@ impl Session {
username = "admin".to_string();
}
- let db = req.guard::<&State<DataAcid>>().await.unwrap();
+ let db = req.guard::<&State<Database>>().await.unwrap();
- let user = {
- let txn = db.inner.begin_read()?;
- let table = txn.open_table(T_USER)?;
- let user = table
- .get(&*username)?
- .ok_or(anyhow!("user not found"))?
- .value()
- .0;
- drop(table);
- user
- };
+ let user = db.get_user(&username)?.ok_or(anyhow!("user not found"))?;
Ok(Session { user })
}
diff --git a/server/src/routes/ui/account/settings.rs b/server/src/routes/ui/account/settings.rs
index 24e90de..06754b1 100644
--- a/server/src/routes/ui/account/settings.rs
+++ b/server/src/routes/ui/account/settings.rs
@@ -5,7 +5,7 @@
*/
use super::{format_form_error, hash_password};
use crate::{
- database::DataAcid,
+ database::Database,
routes::ui::{
account::{rocket_uri_macro_r_account_login, session::Session},
error::MyResult,
@@ -13,11 +13,7 @@ use crate::{
},
uri,
};
-use anyhow::anyhow;
-use jellybase::{
- database::{redb::ReadableTable, Ser, T_USER},
- permission::PermissionSetExt,
-};
+use jellybase::permission::PermissionSetExt;
use jellycommon::user::{PlayerKind, Theme, UserPermission};
use markup::{Render, RenderAttributeValue};
use rocket::{
@@ -117,7 +113,7 @@ pub fn r_account_settings(session: Session) -> DynLayoutPage<'static> {
#[post("/account/settings", data = "<form>")]
pub fn r_account_settings_post(
session: Session,
- database: &State<DataAcid>,
+ database: &State<Database>,
form: Form<Contextual<SettingsForm>>,
) -> MyResult<DynLayoutPage<'static>> {
session
@@ -132,39 +128,29 @@ pub fn r_account_settings_post(
let mut out = String::new();
- let txn = database.begin_write()?;
- let mut users = txn.open_table(T_USER)?;
-
- let mut user = users
- .get(&*session.user.name)?
- .ok_or(anyhow!("user missing"))?
- .value()
- .0;
-
- if let Some(password) = &form.password {
- user.password = hash_password(&session.user.name, password);
- out += "Password updated\n";
- }
- if let Some(display_name) = &form.display_name {
- user.display_name = display_name.clone();
- out += "Display name updated\n";
- }
- if let Some(theme) = form.theme {
- user.theme = theme;
- out += "Theme updated\n";
- }
- if let Some(player_preference) = form.player_preference {
- user.player_preference = player_preference;
- out += "Player preference changed.\n";
- }
- if let Some(native_secret) = &form.native_secret {
- user.native_secret = native_secret.to_owned();
- out += "Native secret updated.\n";
- }
-
- users.insert(&*session.user.name, Ser(user))?;
- drop(users);
- txn.commit()?;
+ database.update_user(&session.user.name, |user| {
+ if let Some(password) = &form.password {
+ user.password = hash_password(&session.user.name, password);
+ out += "Password updated\n";
+ }
+ if let Some(display_name) = &form.display_name {
+ user.display_name = display_name.clone();
+ out += "Display name updated\n";
+ }
+ if let Some(theme) = form.theme {
+ user.theme = theme;
+ out += "Theme updated\n";
+ }
+ if let Some(player_preference) = form.player_preference {
+ user.player_preference = player_preference;
+ out += "Player preference changed.\n";
+ }
+ if let Some(native_secret) = &form.native_secret {
+ user.native_secret = native_secret.to_owned();
+ out += "Native secret updated.\n";
+ }
+ Ok(())
+ })?;
Ok(settings_page(
session, // using the old session here, results in outdated theme being displayed
diff --git a/server/src/routes/ui/admin/mod.rs b/server/src/routes/ui/admin/mod.rs
index 1fba6c0..160999b 100644
--- a/server/src/routes/ui/admin/mod.rs
+++ b/server/src/routes/ui/admin/mod.rs
@@ -6,12 +6,9 @@
pub mod log;
pub mod user;
-use super::{
- account::session::AdminSession,
- assets::{resolve_asset, AVIF_QUALITY, AVIF_SPEED},
-};
+use super::account::session::AdminSession;
use crate::{
- database::DataAcid,
+ database::Database,
routes::ui::{
admin::log::rocket_uri_macro_r_admin_log,
error::MyResult,
@@ -20,16 +17,7 @@ use crate::{
uri,
};
use anyhow::{anyhow, Context};
-use humansize::{format_size, DECIMAL};
-use jellybase::{
- assetfed::AssetInner,
- database::{
- redb::{ReadableTable, ReadableTableMetadata},
- tantivy::query::Bm25StatisticsProvider,
- TableExt, T_INVITE, T_NODE, T_USER_NODE,
- },
- CONF,
-};
+use jellybase::CONF;
use markup::DynRender;
use rand::Rng;
use rocket::{form::Form, get, post, FromForm, State};
@@ -40,28 +28,16 @@ use user::rocket_uri_macro_r_admin_users;
#[get("/admin/dashboard")]
pub async fn r_admin_dashboard(
_session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
) -> MyResult<DynLayoutPage<'static>> {
admin_dashboard(database, None).await
}
pub async fn admin_dashboard<'a>(
- database: &DataAcid,
+ database: &Database,
flash: Option<MyResult<String>>,
) -> MyResult<DynLayoutPage<'a>> {
- let invites = {
- let txn = database.begin_read()?;
- let table = txn.open_table(T_INVITE)?;
- let i = table
- .iter()?
- .map(|a| {
- let (x, _) = a.unwrap();
- x.value().to_owned()
- })
- .collect::<Vec<_>>();
- drop(table);
- i
- };
+ let invites = database.list_invites()?;
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
// let last_import_err = IMPORT_ERRORS.read().await.to_owned();
@@ -129,11 +105,10 @@ pub async fn admin_dashboard<'a>(
#[post("/admin/generate_invite")]
pub async fn r_admin_invite(
_session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
) -> MyResult<DynLayoutPage<'static>> {
let i = format!("{}", rand::rng().random::<u128>());
- T_INVITE.insert(database, &*i, ())?;
-
+ database.create_invite(&i)?;
admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))).await
}
@@ -145,14 +120,13 @@ pub struct DeleteInvite {
#[post("/admin/remove_invite", data = "<form>")]
pub async fn r_admin_remove_invite(
session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
form: Form<DeleteInvite>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
- T_INVITE
- .remove(database, form.invite.as_str())?
- .ok_or(anyhow!("invite did not exist"))?;
-
+ if !database.delete_invite(&form.invite)? {
+ Err(anyhow!("invite does not exist"))?;
+ };
admin_dashboard(database, Some(Ok("Invite invalidated".into()))).await
}
@@ -178,7 +152,7 @@ pub async fn r_admin_remove_invite(
#[post("/admin/delete_cache")]
pub async fn r_admin_delete_cache(
session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
let t = Instant::now();
@@ -202,7 +176,7 @@ fn is_transcoding() -> bool {
#[post("/admin/transcode_posters")]
pub async fn r_admin_transcode_posters(
session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
let _permit = SEM_TRANSCODING
@@ -211,24 +185,25 @@ pub async fn r_admin_transcode_posters(
let t = Instant::now();
- {
- let txn = database.begin_read()?;
- let nodes = txn.open_table(T_NODE)?;
- for node in nodes.iter()? {
- let (_, node) = node?;
- if let Some(poster) = &node.value().0.poster {
- let asset = AssetInner::deser(&poster.0)?;
- if asset.is_federated() {
- continue;
- }
- let source = resolve_asset(asset).await.context("resolving asset")?;
- jellytranscoder::image::transcode(source, AVIF_QUALITY, AVIF_SPEED, 1024)
- .await
- .context("transcoding asset")?;
- }
- }
- }
- drop(_permit);
+ // TODO
+ // {
+ // let txn = database.begin_read()?;
+ // let nodes = txn.open_table(T_NODE)?;
+ // for node in nodes.iter()? {
+ // let (_, node) = node?;
+ // if let Some(poster) = &node.value().0.poster {
+ // let asset = AssetInner::deser(&poster.0)?;
+ // if asset.is_federated() {
+ // continue;
+ // }
+ // let source = resolve_asset(asset).await.context("resolving asset")?;
+ // jellytranscoder::image::transcode(source, AVIF_QUALITY, AVIF_SPEED, 1024)
+ // .await
+ // .context("transcoding asset")?;
+ // }
+ // }
+ // }
+ // drop(_permit);
admin_dashboard(
database,
@@ -240,47 +215,48 @@ pub async fn r_admin_transcode_posters(
.await
}
-fn db_stats(db: &DataAcid) -> anyhow::Result<DynRender> {
- let txn = db.inner.begin_read()?;
- let stats = [
- ("node", txn.open_table(T_NODE)?.stats()?),
- ("user", txn.open_table(T_USER_NODE)?.stats()?),
- ("user-node", txn.open_table(T_USER_NODE)?.stats()?),
- ("invite", txn.open_table(T_INVITE)?.stats()?),
- ];
+fn db_stats(_db: &Database) -> anyhow::Result<DynRender> {
+ // TODO
+ // let txn = db.inner.begin_read()?;
+ // let stats = [
+ // ("node", txn.open_table(T_NODE)?.stats()?),
+ // ("user", txn.open_table(T_USER_NODE)?.stats()?),
+ // ("user-node", txn.open_table(T_USER_NODE)?.stats()?),
+ // ("invite", txn.open_table(T_INVITE)?.stats()?),
+ // ];
- let cache_stats = db.node_index.reader.searcher().doc_store_cache_stats();
- let ft_total_docs = db.node_index.reader.searcher().total_num_docs()?;
+ // let cache_stats = db.node_index.reader.searcher().doc_store_cache_stats();
+ // let ft_total_docs = db.node_index.reader.searcher().total_num_docs()?;
Ok(markup::new! {
- h3 { "Key-Value-Store Statistics" }
- table.border {
- tbody {
- tr {
- th { "table name" }
- th { "tree height" }
- th { "stored bytes" }
- th { "metadata bytes" }
- th { "fragmented bytes" }
- th { "branch pages" }
- th { "leaf pages" }
- }
- @for (name, stats) in &stats { tr {
- td { @name }
- td { @stats.tree_height() }
- td { @format_size(stats.stored_bytes(), DECIMAL) }
- td { @format_size(stats.metadata_bytes(), DECIMAL) }
- td { @format_size(stats.fragmented_bytes(), DECIMAL) }
- td { @stats.branch_pages() }
- td { @stats.leaf_pages() }
- }}
- }
- }
- h3 { "Search Engine Statistics" }
- ul {
- li { "Total documents: " @ft_total_docs }
- li { "Cache misses: " @cache_stats.cache_misses }
- li { "Cache hits: " @cache_stats.cache_hits }
- }
+ // h3 { "Key-Value-Store Statistics" }
+ // table.border {
+ // tbody {
+ // tr {
+ // th { "table name" }
+ // th { "tree height" }
+ // th { "stored bytes" }
+ // th { "metadata bytes" }
+ // th { "fragmented bytes" }
+ // th { "branch pages" }
+ // th { "leaf pages" }
+ // }
+ // @for (name, stats) in &stats { tr {
+ // td { @name }
+ // td { @stats.tree_height() }
+ // td { @format_size(stats.stored_bytes(), DECIMAL) }
+ // td { @format_size(stats.metadata_bytes(), DECIMAL) }
+ // td { @format_size(stats.fragmented_bytes(), DECIMAL) }
+ // td { @stats.branch_pages() }
+ // td { @stats.leaf_pages() }
+ // }}
+ // }
+ // }
+ // h3 { "Search Engine Statistics" }
+ // ul {
+ // li { "Total documents: " @ft_total_docs }
+ // li { "Cache misses: " @cache_stats.cache_misses }
+ // li { "Cache hits: " @cache_stats.cache_hits }
+ // }
})
}
diff --git a/server/src/routes/ui/admin/user.rs b/server/src/routes/ui/admin/user.rs
index 524f849..7ba6d4e 100644
--- a/server/src/routes/ui/admin/user.rs
+++ b/server/src/routes/ui/admin/user.rs
@@ -4,7 +4,7 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::{
- database::DataAcid,
+ database::Database,
routes::ui::{
account::session::AdminSession,
error::MyResult,
@@ -13,36 +13,23 @@ use crate::{
uri,
};
use anyhow::{anyhow, Context};
-use jellybase::database::{redb::ReadableTable, Ser, TableExt, T_USER};
use jellycommon::user::{PermissionSet, UserPermission};
use rocket::{form::Form, get, post, FromForm, FromFormField, State};
#[get("/admin/users")]
pub fn r_admin_users(
_session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
) -> MyResult<DynLayoutPage<'static>> {
user_management(database, None)
}
fn user_management<'a>(
- database: &DataAcid,
+ database: &Database,
flash: Option<MyResult<String>>,
) -> MyResult<DynLayoutPage<'a>> {
// TODO this doesnt scale, pagination!
- let users = {
- let txn = database.begin_read()?;
- let table = txn.open_table(T_USER)?;
- let i = table
- .iter()?
- .map(|a| {
- let (x, y) = a.unwrap();
- (x.value().to_owned(), y.value().0)
- })
- .collect::<Vec<_>>();
- drop(table);
- i
- };
+ let users = database.list_users()?;
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
Ok(LayoutPage {
@@ -51,7 +38,7 @@ fn user_management<'a>(
h1 { "User Management" }
@FlashDisplay { flash: flash.clone() }
h2 { "All Users" }
- ul { @for (_, u) in &users {
+ ul { @for u in &users {
li {
a[href=uri!(r_admin_user(&u.name))] { @format!("{:?}", u.display_name) " (" @u.name ")" }
}
@@ -64,19 +51,19 @@ fn user_management<'a>(
#[get("/admin/user/<name>")]
pub fn r_admin_user<'a>(
_session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
name: &'a str,
) -> MyResult<DynLayoutPage<'a>> {
manage_single_user(database, None, name.to_string())
}
fn manage_single_user<'a>(
- database: &DataAcid,
+ database: &Database,
flash: Option<MyResult<String>>,
name: String,
) -> MyResult<DynLayoutPage<'a>> {
- let user = T_USER
- .get(database, &*name)?
+ let user = database
+ .get_user(&name)?
.ok_or(anyhow!("user does not exist"))?;
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
@@ -152,31 +139,21 @@ pub enum GrantState {
#[post("/admin/update_user_permission", data = "<form>")]
pub fn r_admin_user_permission(
session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
form: Form<UserPermissionForm>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
let perm = serde_json::from_str::<UserPermission>(&form.permission)
.context("parsing provided permission")?;
- let txn = database.begin_write()?;
- let mut users = txn.open_table(T_USER)?;
-
- let mut user = users
- .get(&*form.name)?
- .ok_or(anyhow!("user missing"))?
- .value()
- .0;
-
- match form.action {
- GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)),
- GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)),
- GrantState::Unset => drop(user.permissions.0.remove(&perm)),
- }
-
- users.insert(&*form.name, Ser(user))?;
- drop(users);
- txn.commit()?;
+ database.update_user(&form.name, |user| {
+ match form.action {
+ GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)),
+ GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)),
+ GrantState::Unset => drop(user.permissions.0.remove(&perm)),
+ }
+ Ok(())
+ })?;
manage_single_user(
database,
@@ -188,12 +165,12 @@ pub fn r_admin_user_permission(
#[post("/admin/remove_user", data = "<form>")]
pub fn r_admin_remove_user(
session: AdminSession,
- database: &State<DataAcid>,
+ database: &State<Database>,
form: Form<DeleteUser>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
- T_USER
- .remove(database, form.name.as_str())?
- .ok_or(anyhow!("user did not exist"))?;
+ if !database.delete_user(&form.name)? {
+ Err(anyhow!("user did not exist"))?;
+ }
user_management(database, Some(Ok("User removed".into())))
}
diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs
index 7eb8e98..689c7f1 100644
--- a/server/src/routes/ui/assets.rs
+++ b/server/src/routes/ui/assets.rs
@@ -7,12 +7,7 @@ use crate::routes::ui::{account::session::Session, error::MyResult, CacheControl
use anyhow::{anyhow, Context};
use base64::Engine;
use jellybase::{
- assetfed::AssetInner,
- cache::async_cache_file,
- database::{DataAcid, TableExt, T_NODE},
- federation::Federation,
- permission::NodePermissionExt,
- CONF,
+ assetfed::AssetInner, cache::async_cache_file, database::Database, federation::Federation, CONF,
};
use jellycommon::{LocalTrack, PeopleGroup, SourceTrackKind, TrackSource};
use log::info;
@@ -67,23 +62,21 @@ pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> {
#[get("/n/<id>/poster?<width>")]
pub async fn r_item_poster(
- session: Session,
- db: &State<DataAcid>,
+ _session: Session,
+ db: &State<Database>,
id: &str,
width: Option<usize>,
) -> MyResult<Redirect> {
- let node = T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
+ // TODO perm
+ let node = db
+ .get_node_slug(id)?
.ok_or(anyhow!("node does not exist"))?;
- let mut asset = node.poster;
+ let mut asset = node.poster.clone();
if asset.is_none() {
- if let Some(parent) = &node.parents.last() {
- let parent = T_NODE
- .get(db, parent.as_str())?
- .ok_or(anyhow!("node does not exist"))?;
- asset = parent.poster;
+ if let Some(parent) = node.parents.last().copied() {
+ let parent = db.get_node(parent)?.ok_or(anyhow!("node does not exist"))?;
+ asset = parent.poster.clone();
}
};
let asset = asset.unwrap_or_else(|| {
@@ -94,23 +87,21 @@ pub async fn r_item_poster(
}
#[get("/n/<id>/backdrop?<width>")]
pub async fn r_item_backdrop(
- session: Session,
- db: &State<DataAcid>,
+ _session: Session,
+ db: &State<Database>,
id: &str,
width: Option<usize>,
) -> MyResult<Redirect> {
- let node = T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
+ // TODO perm
+ let node = db
+ .get_node_slug(id)?
.ok_or(anyhow!("node does not exist"))?;
- let mut asset = node.poster;
+ let mut asset = node.backdrop.clone();
if asset.is_none() {
- if let Some(parent) = &node.parents.last() {
- let parent = T_NODE
- .get(db, parent.as_str())?
- .ok_or(anyhow!("node does not exist"))?;
- asset = parent.poster;
+ if let Some(parent) = node.parents.last().copied() {
+ let parent = db.get_node(parent)?.ok_or(anyhow!("node does not exist"))?;
+ asset = parent.backdrop.clone();
}
};
let asset = asset.unwrap_or_else(|| {
@@ -122,19 +113,18 @@ pub async fn r_item_backdrop(
#[get("/n/<id>/person/<index>/asset?<group>&<width>")]
pub async fn r_person_asset(
- session: Session,
- db: &State<DataAcid>,
+ _session: Session,
+ db: &State<Database>,
id: &str,
index: usize,
group: String,
width: Option<usize>,
) -> MyResult<Redirect> {
- T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
- .ok_or(anyhow!("node does not exist"))?;
+ // TODO perm
- let node = T_NODE.get(db, id)?.unwrap_or_default();
+ let node = db
+ .get_node_slug(id)?
+ .ok_or(anyhow!("node does not exist"))?;
let app = node
.people
.get(&PeopleGroup::from_str(&group).map_err(|()| anyhow!("unknown people group"))?)
@@ -155,19 +145,18 @@ pub async fn r_person_asset(
#[get("/n/<id>/thumbnail?<t>&<width>")]
pub async fn r_node_thumbnail(
- session: Session,
- db: &State<DataAcid>,
+ _session: Session,
+ db: &State<Database>,
fed: &State<Federation>,
id: &str,
t: f64,
width: Option<usize>,
) -> MyResult<Redirect> {
- let node = T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
+ let node = db
+ .get_node_slug(id)?
.ok_or(anyhow!("node does not exist"))?;
- let media = node.media.ok_or(anyhow!("no media"))?;
+ let media = node.media.as_ref().ok_or(anyhow!("no media"))?;
let (thumb_track_index, thumb_track) = media
.tracks
.iter()
diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs
index 9a5fb6c..a15dc27 100644
--- a/server/src/routes/ui/browser.rs
+++ b/server/src/routes/ui/browser.rs
@@ -10,8 +10,7 @@ use super::{
node::NodeCard,
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
-use crate::{database::DataAcid, uri};
-use jellybase::database::{redb::ReadableTable, T_NODE, T_USER_NODE};
+use crate::{database::Database, uri};
use rocket::{get, State};
/// This function is a stub and only useful for use in the uri! macro.
@@ -21,30 +20,11 @@ pub fn r_all_items() {}
#[get("/items?<page>&<filter..>")]
pub fn r_all_items_filter(
sess: Session,
- db: &State<DataAcid>,
+ db: &State<Database>,
page: Option<usize>,
filter: NodeFilterSort,
) -> Result<DynLayoutPage<'_>, MyError> {
- let mut items = {
- let txn = db.begin_read()?;
- let nodes = txn.open_table(T_NODE)?;
- let node_users = txn.open_table(T_USER_NODE)?;
- let i = nodes
- .iter()?
- .map(|a| {
- let (x, y) = a.unwrap();
- let (x, y) = (x.value().to_owned(), y.value().0);
- let z = node_users
- .get(&(sess.user.name.as_str(), x.as_str()))
- .unwrap()
- .map(|z| z.value().0)
- .unwrap_or_default();
- (x, y, z)
- })
- .collect::<Vec<_>>();
- drop(nodes);
- i
- };
+ let mut items = db.list_nodes_with_udata(sess.user.name.as_str())?;
filter_and_sort_nodes(
&filter,
@@ -65,8 +45,8 @@ pub fn r_all_items_filter(
.page.dir {
h1 { "All Items" }
@NodeFilterSortForm { f: &filter }
- ul.children { @for (id, node, udata) in &items[from..to] {
- li {@NodeCard { id, node, udata }}
+ ul.children { @for (node, udata) in &items[from..to] {
+ li {@NodeCard { node, udata }}
}}
p.pagecontrols {
span.current { "Page " @{page + 1} " of " @max_page " " }
diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs
index 67924e2..ee593a2 100644
--- a/server/src/routes/ui/error.rs
+++ b/server/src/routes/ui/error.rs
@@ -102,45 +102,3 @@ impl From<serde_json::Error> for MyError {
MyError(anyhow::anyhow!("{err}"))
}
}
-impl From<jellybase::database::redb::CommitError> for MyError {
- fn from(err: jellybase::database::redb::CommitError) -> Self {
- MyError(anyhow::anyhow!("database oopsie during commit: {err}"))
- }
-}
-impl From<jellybase::database::redb::CompactionError> for MyError {
- fn from(err: jellybase::database::redb::CompactionError) -> Self {
- MyError(anyhow::anyhow!("database oopsie during compaction: {err}"))
- }
-}
-impl From<jellybase::database::redb::DatabaseError> for MyError {
- fn from(err: jellybase::database::redb::DatabaseError) -> Self {
- MyError(anyhow::anyhow!("generic database oopsie: {err}"))
- }
-}
-impl From<jellybase::database::redb::SavepointError> for MyError {
- fn from(err: jellybase::database::redb::SavepointError) -> Self {
- MyError(anyhow::anyhow!(
- "database oopsie during savepointing: {err}"
- ))
- }
-}
-impl From<jellybase::database::redb::StorageError> for MyError {
- fn from(err: jellybase::database::redb::StorageError) -> Self {
- MyError(anyhow::anyhow!("database oopsie, storage error: {err}"))
- }
-}
-impl From<jellybase::database::redb::TableError> for MyError {
- fn from(err: jellybase::database::redb::TableError) -> Self {
- MyError(anyhow::anyhow!("database oopsie, table error: {err}"))
- }
-}
-impl From<jellybase::database::redb::TransactionError> for MyError {
- fn from(err: jellybase::database::redb::TransactionError) -> Self {
- MyError(anyhow::anyhow!("database oopsie during transaction: {err}"))
- }
-}
-impl From<jellybase::database::tantivy::TantivyError> for MyError {
- fn from(err: jellybase::database::tantivy::TantivyError) -> Self {
- MyError(anyhow::anyhow!("database during search: {err}"))
- }
-}
diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs
index 8eacfde..ebed647 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/routes/ui/home.rs
@@ -5,40 +5,18 @@
*/
use super::{account::session::Session, layout::LayoutPage, node::NodeCard};
use crate::{
- database::DataAcid,
+ database::Database,
routes::ui::{error::MyResult, layout::DynLayoutPage},
};
use chrono::{Datelike, Utc};
-use jellybase::{
- database::{redb::ReadableTable, T_NODE, T_USER_NODE},
- CONF,
-};
+use jellybase::CONF;
use jellycommon::{user::WatchedState, Rating};
use rocket::{get, State};
use tokio::fs::read_to_string;
#[get("/")]
-pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
- let mut items = {
- let txn = db.begin_read()?;
- let nodes = txn.open_table(T_NODE)?;
- let node_users = txn.open_table(T_USER_NODE)?;
- let i = nodes
- .iter()?
- .map(|a| {
- let (x, y) = a.unwrap();
- let (x, y) = (x.value().to_owned(), y.value().0);
- let z = node_users
- .get(&(sess.user.name.as_str(), x.as_str()))
- .unwrap()
- .map(|z| z.value().0)
- .unwrap_or_default();
- (x, y, z)
- })
- .collect::<Vec<_>>();
- drop(nodes);
- i
- };
+pub fn r_home(sess: Session, db: &State<Database>) -> MyResult<DynLayoutPage> {
+ let mut items = db.list_nodes_with_udata(&sess.user.name)?;
let random = (0..16)
.flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone()))
.collect::<Vec<_>>();
@@ -52,7 +30,7 @@ pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
// .into_iter()
// .collect::<Vec<_>>();
- items.sort_by_key(|(_, n, _)| {
+ items.sort_by_key(|(n, _)| {
n.ratings
.get(&Rating::Tmdb)
.map(|x| (*x * -1000.) as i32)
@@ -62,11 +40,11 @@ pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
let top_rated = items
.iter()
.take(16)
- .filter(|(_, n, _)| n.ratings.contains_key(&Rating::Tmdb))
+ .filter(|(n, _)| n.ratings.contains_key(&Rating::Tmdb))
.map(|k| k.to_owned())
.collect::<Vec<_>>();
- items.sort_by_key(|(_, n, _)| n.release_date.map(|d| -d).unwrap_or(i64::MAX));
+ items.sort_by_key(|(n, _)| n.release_date.map(|d| -d).unwrap_or(i64::MAX));
let latest = items
.iter()
@@ -76,13 +54,13 @@ pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
let continue_watching = items
.iter()
- .filter(|(_, _, u)| matches!(u.watched, WatchedState::Progress(_)))
+ .filter(|(_, u)| matches!(u.watched, WatchedState::Progress(_)))
.map(|k| k.to_owned())
.collect::<Vec<_>>();
let watchlist = items
.iter()
- .filter(|(_, _, u)| matches!(u.watched, WatchedState::Pending))
+ .filter(|(_, u)| matches!(u.watched, WatchedState::Pending))
.map(|k| k.to_owned())
.collect::<Vec<_>>();
@@ -95,28 +73,28 @@ pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
// }}
@if !continue_watching.is_empty() {
h2 { "Continue Watching" }
- ul.children.hlist {@for (id, node, udata) in &continue_watching {
- li { @NodeCard { id, node, udata } }
+ ul.children.hlist {@for (node, udata) in &continue_watching {
+ li { @NodeCard { node, udata } }
}}
}
@if !watchlist.is_empty() {
h2 { "Watchlist" }
- ul.children.hlist {@for (id, node, udata) in &watchlist {
- li { @NodeCard { id, node, udata } }
+ ul.children.hlist {@for (node, udata) in &watchlist {
+ li { @NodeCard { node, udata } }
}}
}
h2 { "Today's Picks" }
- ul.children.hlist {@for (id, node, udata) in &random {
- li { @NodeCard { id, node, udata } }
+ ul.children.hlist {@for (node, udata) in &random {
+ li { @NodeCard { node, udata } }
}}
h2 { "Latest Releases" }
- ul.children.hlist {@for (id, node, udata) in &latest {
- li { @NodeCard { id, node, udata } }
+ ul.children.hlist {@for (node, udata) in &latest {
+ li { @NodeCard { node, udata } }
}}
@if !top_rated.is_empty() {
h2 { "Top Rated" }
- ul.children.hlist {@for (id, node, udata) in &top_rated {
- li { @NodeCard { id, node, udata } }
+ ul.children.hlist {@for (node, udata) in &top_rated {
+ li { @NodeCard { node, udata } }
}}
}
},
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index 3307d50..5cc8a2f 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
@@ -12,7 +14,7 @@ use super::{
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm, SortOrder, SortProperty},
};
use crate::{
- database::DataAcid,
+ database::Database,
routes::{
api::AcceptJson,
ui::{
@@ -30,13 +32,9 @@ use crate::{
};
use anyhow::{anyhow, Result};
use chrono::DateTime;
-use jellybase::{
- database::{TableExt, T_NODE, T_USER_NODE},
- permission::NodePermissionExt,
-};
use jellycommon::{
user::{NodeUserData, WatchedState},
- Chapter, MediaInfo, Node, NodeKind, PeopleGroup, Rating, SourceTrackKind,
+ Chapter, MediaInfo, Node, NodeID, NodeKind, PeopleGroup, Rating, SourceTrackKind,
};
use rocket::{get, serde::json::Json, Either, State};
@@ -50,21 +48,14 @@ pub fn r_library_node(id: String) {
pub async fn r_library_node_filter<'a>(
session: Session,
id: &'a str,
- db: &'a State<DataAcid>,
+ db: &'a State<Database>,
aj: AcceptJson,
filter: NodeFilterSort,
) -> MyResult<Either<DynLayoutPage<'a>, Json<Node>>> {
- let node = T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
- .ok_or(anyhow!("node does not exist"))?;
-
- let udata = T_USER_NODE
- .get(db, &(session.user.name.as_str(), id))?
- .unwrap_or_default();
+ let (node, udata) = db.get_node_with_userdata(NodeID::from_slug(id), &session)?;
if *aj {
- return Ok(Either::Right(Json(node)));
+ return Ok(Either::Right(Json((*node).clone())));
}
// let mut children = node
@@ -111,22 +102,22 @@ pub async fn r_library_node_filter<'a>(
}
markup::define! {
- NodeCard<'a>(id: &'a str, node: &'a Node, udata: &'a NodeUserData) {
+ NodeCard<'a>(node: &'a Node, udata: &'a NodeUserData) {
@let cls = format!("node card poster {}", aspect_class(node.kind.unwrap_or_default()));
div[class=cls] {
.poster {
- a[href=uri!(r_library_node(id))] {
- img[src=uri!(r_item_poster(id, Some(1024))), loading="lazy"];
+ a[href=uri!(r_library_node(&node.slug))] {
+ img[src=uri!(r_item_poster(&node.slug, Some(1024))), loading="lazy"];
}
.cardhover.item {
@if node.media.is_some() {
- a.play.icon[href=&uri!(r_player(id, PlayerConfig::default()))] { "play_arrow" }
+ a.play.icon[href=&uri!(r_player(&node.slug, PlayerConfig::default()))] { "play_arrow" }
}
@Props { node, udata, full: false }
}
}
div.title {
- a[href=uri!(r_library_node(id))] {
+ a[href=uri!(r_library_node(&node.slug))] {
@node.title
}
}
@@ -137,7 +128,7 @@ markup::define! {
}
}
}
- NodePage<'a>(id: &'a str, node: &'a Node, udata: &'a NodeUserData, children: &'a [(String, Node, NodeUserData)], path: &'a [(String, Node)], filter: &'a NodeFilterSort) {
+ NodePage<'a>(id: &'a str, node: &'a Node, udata: &'a NodeUserData, children: &'a [(Arc<Node>, NodeUserData)], path: &'a [(String, Node)], filter: &'a NodeFilterSort) {
@if !matches!(node.kind.unwrap_or_default(), NodeKind::Collection) {
img.backdrop[src=uri!(r_item_backdrop(id, Some(2048))), loading="lazy"];
}
@@ -240,13 +231,13 @@ markup::define! {
}
@match node.kind.unwrap_or_default() {
NodeKind::Show | NodeKind::Series | NodeKind::Season => {
- ol { @for (id, c, _) in children.iter() {
- li { a[href=uri!(r_library_node(id))] { @c.title } }
+ ol { @for (c, _) in children.iter() {
+ li { a[href=uri!(r_library_node(&c.slug))] { @c.title } }
}}
}
NodeKind::Collection | NodeKind::Channel | _ => {
- ul.children {@for (id, node, udata) in children.iter() {
- li { @NodeCard { id, node, udata } }
+ ul.children {@for (node, udata) in children.iter() {
+ li { @NodeCard { node, udata } }
}}
}
}
@@ -324,24 +315,19 @@ pub fn format_duration(mut d: f64) -> String {
pub trait DatabaseNodeUserDataExt {
fn get_node_with_userdata(
&self,
- id: &str,
+ id: NodeID,
session: &Session,
- ) -> Result<(String, Node, NodeUserData)>;
+ ) -> Result<(Arc<Node>, NodeUserData)>;
}
-impl DatabaseNodeUserDataExt for DataAcid {
+impl DatabaseNodeUserDataExt for Database {
fn get_node_with_userdata(
&self,
- id: &str,
+ id: NodeID,
session: &Session,
- ) -> Result<(String, Node, NodeUserData)> {
+ ) -> Result<(Arc<Node>, NodeUserData)> {
Ok((
- id.to_owned(),
- T_NODE
- .get(self, id)?
- .only_if_permitted(&session.user.permissions)
- .ok_or(anyhow!("node does not exist: {id}"))?,
- T_USER_NODE
- .get(self, &(session.user.name.as_str(), id))?
+ self.get_node(id)?.ok_or(anyhow!("node does not exist"))?,
+ self.get_node_udata(id, &session.user.name)?
.unwrap_or_default(),
))
}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index 178cbba..b24e5e9 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -8,7 +8,7 @@ use super::{
layout::LayoutPage,
};
use crate::{
- database::DataAcid,
+ database::Database,
routes::{
stream::rocket_uri_macro_r_stream,
ui::{assets::rocket_uri_macro_r_item_backdrop, error::MyResult, layout::DynLayoutPage},
@@ -16,11 +16,7 @@ use crate::{
uri,
};
use anyhow::anyhow;
-use jellybase::{
- database::{TableExt, T_NODE},
- permission::PermissionSetExt,
- CONF,
-};
+use jellybase::{permission::PermissionSetExt, CONF};
use jellycommon::{
stream::{StreamFormat, StreamSpec},
user::{PermissionSet, PlayerKind, UserPermission},
@@ -28,6 +24,7 @@ use jellycommon::{
};
use markup::DynRender;
use rocket::{get, response::Redirect, Either, FromForm, State, UriDisplayQuery};
+use std::sync::Arc;
#[derive(FromForm, Default, Clone, Debug, UriDisplayQuery)]
pub struct PlayerConfig {
@@ -63,11 +60,13 @@ fn jellynative_url(action: &str, seek: f64, secret: &str, node: &str, session: &
#[get("/n/<id>/player?<conf..>", rank = 4)]
pub fn r_player<'a>(
sess: Session,
- db: &'a State<DataAcid>,
+ db: &'a State<Database>,
id: &'a str,
conf: PlayerConfig,
) -> MyResult<Either<DynLayoutPage<'a>, Redirect>> {
- let item = T_NODE.get(db, id)?.ok_or(anyhow!("node does not exist"))?;
+ let item = db
+ .get_node_slug(id)?
+ .ok_or(anyhow!("node does not exist"))?;
let native_session = |action: &str| {
let perm = [
@@ -130,7 +129,7 @@ pub fn r_player<'a>(
}))
}
-pub fn player_conf<'a>(item: Node, playing: bool) -> anyhow::Result<DynRender<'a>> {
+pub fn player_conf<'a>(item: Arc<Node>, playing: bool) -> anyhow::Result<DynRender<'a>> {
let mut audio_tracks = vec![];
let mut video_tracks = vec![];
let mut sub_tracks = vec![];
diff --git a/server/src/routes/ui/search.rs b/server/src/routes/ui/search.rs
index c1f9865..ac37b80 100644
--- a/server/src/routes/ui/search.rs
+++ b/server/src/routes/ui/search.rs
@@ -2,63 +2,27 @@ use super::{
account::session::Session,
error::MyResult,
layout::{DynLayoutPage, LayoutPage},
- node::NodeCard,
-};
-use anyhow::{anyhow, Context};
-use jellybase::{
- database::{
- tantivy::{
- collector::{Count, TopDocs},
- query::QueryParser,
- schema::Value,
- TantivyDocument,
- },
- DataAcid, TableExt, T_NODE, T_USER_NODE,
- },
- permission::NodePermissionExt,
+ node::{DatabaseNodeUserDataExt, NodeCard},
};
+use jellybase::database::Database;
use rocket::{get, State};
use std::time::Instant;
#[get("/search?<query>&<page>")]
pub async fn r_search<'a>(
session: Session,
- db: &State<DataAcid>,
+ db: &State<Database>,
query: Option<&str>,
page: Option<usize>,
) -> MyResult<DynLayoutPage<'a>> {
let timing = Instant::now();
let results = if let Some(query) = query {
- let query = QueryParser::for_index(
- &db.node_index.index,
- vec![db.node_index.title, db.node_index.description],
- )
- .parse_query(query)
- .context("parsing query")?;
-
- let searcher = db.node_index.reader.searcher();
- let sres = searcher.search(
- &query,
- &TopDocs::with_limit(32).and_offset(page.unwrap_or_default() * 32),
- )?;
- let scount = searcher.search(&query, &Count)?;
-
- let mut results = Vec::new();
- for (_, daddr) in sres {
- let doc: TantivyDocument = searcher.doc(daddr)?;
- let id = doc.get_first(db.node_index.id).unwrap().as_str().unwrap();
-
- let node = T_NODE
- .get(db, id)?
- .only_if_permitted(&session.user.permissions)
- .ok_or(anyhow!("node does not exist"))?;
- let udata = T_USER_NODE
- .get(db, &(session.user.name.as_str(), id))?
- .unwrap_or_default();
-
- results.push((id.to_owned(), node, udata));
- }
- Some((scount, results))
+ let (count, ids) = db.search(query, page.unwrap_or_default())?;
+ let nodes = ids
+ .into_iter()
+ .map(|id| db.get_node_with_userdata(id, &session))
+ .collect::<Result<Vec<_>, anyhow::Error>>()?;
+ Some((count, nodes))
} else {
None
};
@@ -77,8 +41,8 @@ pub async fn r_search<'a>(
@if let Some((count, results)) = &results {
h2 { "Results" }
p.stats { @format!("Found {count} nodes in {search_dur:?}.") }
- ul.children {@for (id, node, udata) in results.iter() {
- li { @NodeCard { id, node, udata } }
+ ul.children {@for (node, udata) in results.iter() {
+ li { @NodeCard { node, udata } }
}}
// TODO pagination
}
diff --git a/server/src/routes/ui/sort.rs b/server/src/routes/ui/sort.rs
index bb71184..705b616 100644
--- a/server/src/routes/ui/sort.rs
+++ b/server/src/routes/ui/sort.rs
@@ -134,10 +134,10 @@ pub enum SortOrder {
pub fn filter_and_sort_nodes(
f: &NodeFilterSort,
default_sort: (SortProperty, SortOrder),
- nodes: &mut Vec<(String, Node, NodeUserData)>,
+ nodes: &mut Vec<(Node, NodeUserData)>,
) {
let sort_prop = f.sort_by.unwrap_or(default_sort.0);
- nodes.retain(|(_id, node, udata)| {
+ nodes.retain(|(node, udata)| {
let mut o = true;
if let Some(prop) = &f.filter_kind {
for p in FilterProperty::ALL {
@@ -175,34 +175,32 @@ pub fn filter_and_sort_nodes(
});
match sort_prop {
SortProperty::Duration => {
- nodes.sort_by_key(|(_, n, _)| (n.media.as_ref().unwrap().duration * 1000.) as i64)
+ nodes.sort_by_key(|(n, _)| (n.media.as_ref().unwrap().duration * 1000.) as i64)
}
SortProperty::ReleaseDate => {
- nodes.sort_by_key(|(_, n, _)| n.release_date.expect("asserted above"))
+ nodes.sort_by_key(|(n, _)| n.release_date.expect("asserted above"))
}
- SortProperty::Title => nodes.sort_by(|(_, a, _), (_, b, _)| a.title.cmp(&b.title)),
- SortProperty::RatingRottenTomatoes => nodes.sort_by_cached_key(|(_, n, _)| {
+ SortProperty::Title => nodes.sort_by(|(a, _), (b, _)| a.title.cmp(&b.title)),
+ SortProperty::RatingRottenTomatoes => nodes.sort_by_cached_key(|(n, _)| {
SortAnyway(*n.ratings.get(&Rating::RottenTomatoes).unwrap_or(&0.))
}),
- SortProperty::RatingMetacritic => nodes.sort_by_cached_key(|(_, n, _)| {
+ SortProperty::RatingMetacritic => nodes.sort_by_cached_key(|(n, _)| {
SortAnyway(*n.ratings.get(&Rating::Metacritic).unwrap_or(&0.))
}),
- SortProperty::RatingImdb => nodes.sort_by_cached_key(|(_, n, _)| {
- SortAnyway(*n.ratings.get(&Rating::Imdb).unwrap_or(&0.))
- }),
- SortProperty::RatingTmdb => nodes.sort_by_cached_key(|(_, n, _)| {
- SortAnyway(*n.ratings.get(&Rating::Tmdb).unwrap_or(&0.))
- }),
- SortProperty::RatingYoutubeViews => nodes.sort_by_cached_key(|(_, n, _)| {
+ SortProperty::RatingImdb => nodes
+ .sort_by_cached_key(|(n, _)| SortAnyway(*n.ratings.get(&Rating::Imdb).unwrap_or(&0.))),
+ SortProperty::RatingTmdb => nodes
+ .sort_by_cached_key(|(n, _)| SortAnyway(*n.ratings.get(&Rating::Tmdb).unwrap_or(&0.))),
+ SortProperty::RatingYoutubeViews => nodes.sort_by_cached_key(|(n, _)| {
SortAnyway(*n.ratings.get(&Rating::YoutubeViews).unwrap_or(&0.))
}),
- SortProperty::RatingYoutubeLikes => nodes.sort_by_cached_key(|(_, n, _)| {
+ SortProperty::RatingYoutubeLikes => nodes.sort_by_cached_key(|(n, _)| {
SortAnyway(*n.ratings.get(&Rating::YoutubeLikes).unwrap_or(&0.))
}),
- SortProperty::RatingYoutubeFollowers => nodes.sort_by_cached_key(|(_, n, _)| {
+ SortProperty::RatingYoutubeFollowers => nodes.sort_by_cached_key(|(n, _)| {
SortAnyway(*n.ratings.get(&Rating::YoutubeFollowers).unwrap_or(&0.))
}),
- SortProperty::RatingUser => nodes.sort_by_cached_key(|(_, _, u)| u.rating),
+ SortProperty::RatingUser => nodes.sort_by_cached_key(|(_, u)| u.rating),
}
match f.sort_order.unwrap_or(default_sort.1) {