diff options
author | metamuffin <metamuffin@disroot.org> | 2025-05-26 18:24:16 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-05-26 18:24:16 +0200 |
commit | 3b15caade07e8fbe351fed9aceb3f435bf58368e (patch) | |
tree | cce91c229b78061ad36f29d76a76d67c3c737c59 /logic/src | |
parent | 1eeff5c03e8985d16d4f2b6283741dd82b369bd3 (diff) | |
download | jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar.bz2 jellything-3b15caade07e8fbe351fed9aceb3f435bf58368e.tar.zst |
move all direct database access to logic crate
Diffstat (limited to 'logic/src')
-rw-r--r-- | logic/src/account.rs | 60 | ||||
-rw-r--r-- | logic/src/admin/mod.rs | 40 | ||||
-rw-r--r-- | logic/src/admin/user.rs | 46 | ||||
-rw-r--r-- | logic/src/assets.rs | 131 | ||||
-rw-r--r-- | logic/src/home.rs | 11 | ||||
-rw-r--r-- | logic/src/items.rs | 6 | ||||
-rw-r--r-- | logic/src/lib.rs | 17 | ||||
-rw-r--r-- | logic/src/login.rs | 10 | ||||
-rw-r--r-- | logic/src/node.rs | 85 | ||||
-rw-r--r-- | logic/src/search.rs | 14 | ||||
-rw-r--r-- | logic/src/session.rs | 19 | ||||
-rw-r--r-- | logic/src/stats.rs | 7 |
12 files changed, 385 insertions, 61 deletions
diff --git a/logic/src/account.rs b/logic/src/account.rs new file mode 100644 index 0000000..a352437 --- /dev/null +++ b/logic/src/account.rs @@ -0,0 +1,60 @@ +/* + 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. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ + +use crate::{DATABASE, login::hash_password, session::Session}; +use anyhow::Result; +use jellycommon::user::{PlayerKind, Theme, User}; + +pub fn update_user_password(session: &Session, password: &str) -> Result<()> { + DATABASE.update_user(&session.user.name, |user| { + user.password = hash_password(&session.user.name, password); + Ok(()) + })?; + Ok(()) +} +pub fn update_user_display_name(session: &Session, display_name: &str) -> Result<()> { + DATABASE.update_user(&session.user.name, |user| { + user.display_name = display_name.to_owned(); + Ok(()) + })?; + Ok(()) +} +pub fn update_user_native_secret(session: &Session, native_secret: &str) -> Result<()> { + DATABASE.update_user(&session.user.name, |user| { + user.native_secret = native_secret.to_owned(); + Ok(()) + })?; + Ok(()) +} +pub fn update_user_theme(session: &Session, theme: Theme) -> Result<()> { + DATABASE.update_user(&session.user.name, |user| { + user.theme = theme; + Ok(()) + })?; + Ok(()) +} +pub fn update_user_player_preference( + session: &Session, + player_preference: PlayerKind, +) -> Result<()> { + DATABASE.update_user(&session.user.name, |user| { + user.player_preference = player_preference; + Ok(()) + })?; + Ok(()) +} +pub fn register_user(invitation: &str, username: &str, password: &str) -> Result<()> { + DATABASE.register_user( + &invitation, + &username, + User { + display_name: username.to_owned(), + name: username.to_owned(), + password: hash_password(&username, &password), + ..Default::default() + }, + ) +} diff --git a/logic/src/admin/mod.rs b/logic/src/admin/mod.rs index 2545ba4..804cb2b 100644 --- a/logic/src/admin/mod.rs +++ b/logic/src/admin/mod.rs @@ -7,14 +7,42 @@ pub mod log; pub mod user; -use crate::session::AdminSession; -use anyhow::Result; -use jellydb::Database; -use jellyimport::IMPORT_ERRORS; +use crate::{DATABASE, session::AdminSession}; +use anyhow::{Result, anyhow}; +use jellyimport::{IMPORT_ERRORS, import_wrap}; +use rand::Rng; +use std::time::{Duration, Instant}; +use tokio::task::spawn_blocking; pub async fn get_import_errors(_session: &AdminSession) -> Vec<String> { IMPORT_ERRORS.read().await.to_owned() } -pub fn list_invites(_session: &AdminSession, database: &Database) -> Result<Vec<String>> { - database.list_invites() +pub fn list_invites(_session: &AdminSession) -> Result<Vec<String>> { + DATABASE.list_invites() +} + +pub fn create_invite(_session: &AdminSession) -> Result<String> { + let i = format!("{}", rand::rng().random::<u128>()); + DATABASE.create_invite(&i)?; + Ok(i) +} +pub fn delete_invite(_session: &AdminSession, invite: &str) -> Result<()> { + if !DATABASE.delete_invite(invite)? { + Err(anyhow!("invite does not exist"))?; + }; + Ok(()) +} +pub async fn update_search_index(_session: &AdminSession) -> Result<()> { + spawn_blocking(move || DATABASE.search_create_index()).await? +} +pub async fn do_import( + _session: &AdminSession, + incremental: bool, +) -> Result<(Duration, Result<()>)> { + let t = Instant::now(); + if !incremental { + DATABASE.clear_nodes()?; + } + let r = import_wrap((*DATABASE).clone(), incremental).await; + Ok((t.elapsed(), r)) } diff --git a/logic/src/admin/user.rs b/logic/src/admin/user.rs index 3ec3852..e277077 100644 --- a/logic/src/admin/user.rs +++ b/logic/src/admin/user.rs @@ -4,14 +4,48 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::session::AdminSession; -use anyhow::Result; -use jellycommon::api::ApiAdminUsersResponse; -use jellydb::Database; +use crate::{DATABASE, session::AdminSession}; +use anyhow::{Result, anyhow}; +use jellycommon::{ + api::ApiAdminUsersResponse, + user::{User, UserPermission}, +}; -pub fn admin_users(db: &Database, _session: &AdminSession) -> Result<ApiAdminUsersResponse> { +pub fn admin_users(_session: &AdminSession) -> Result<ApiAdminUsersResponse> { // TODO dont return useless info like passwords Ok(ApiAdminUsersResponse { - users: db.list_users()?, + users: DATABASE.list_users()?, + }) +} +pub fn get_user(_session: &AdminSession, username: &str) -> Result<User> { + DATABASE + .get_user(username)? + .ok_or(anyhow!("user not found")) +} +pub fn delete_user(_session: &AdminSession, username: &str) -> Result<()> { + if !DATABASE.delete_user(&username)? { + Err(anyhow!("user did not exist"))?; + } + Ok(()) +} + +pub enum GrantState { + Grant, + Revoke, + Unset, +} +pub fn update_user_perms( + _session: &AdminSession, + username: &str, + perm: UserPermission, + action: GrantState, +) -> Result<()> { + DATABASE.update_user(username, |user| { + match 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(()) }) } diff --git a/logic/src/assets.rs b/logic/src/assets.rs new file mode 100644 index 0000000..7be3845 --- /dev/null +++ b/logic/src/assets.rs @@ -0,0 +1,131 @@ +/* + 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. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ + +use crate::{DATABASE, session::Session}; +use anyhow::{Result, anyhow}; +use jellycommon::{Asset, LocalTrack, NodeID, PeopleGroup, SourceTrackKind, TrackSource}; +use jellyimport_asset_token::AssetInner; + +pub fn get_node_backdrop(_session: &Session, id: NodeID) -> Result<Asset> { + // TODO perm + let node = DATABASE + .get_node(id)? + .ok_or(anyhow!("node does not exist"))?; + + let mut asset = node.backdrop.clone(); + if asset.is_none() { + if let Some(parent) = node.parents.last().copied() { + let parent = DATABASE + .get_node(parent)? + .ok_or(anyhow!("node does not exist"))?; + asset = parent.backdrop.clone(); + } + }; + Ok(asset.unwrap_or_else(|| { + AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() + })) +} +pub fn get_node_poster(_session: &Session, id: NodeID) -> Result<Asset> { + // TODO perm + let node = DATABASE + .get_node(id)? + .ok_or(anyhow!("node does not exist"))?; + + let mut asset = node.poster.clone(); + if asset.is_none() { + if let Some(parent) = node.parents.last().copied() { + let parent = DATABASE + .get_node(parent)? + .ok_or(anyhow!("node does not exist"))?; + asset = parent.poster.clone(); + } + }; + Ok(asset.unwrap_or_else(|| { + AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() + })) +} + +pub fn get_node_person_asset( + _session: &Session, + id: NodeID, + group: PeopleGroup, + index: usize, +) -> Result<Asset> { + // TODO perm + + let node = DATABASE + .get_node(id)? + .ok_or(anyhow!("node does not exist"))?; + let app = node + .people + .get(&group) + .ok_or(anyhow!("group has no members"))? + .get(index) + .ok_or(anyhow!("person does not exist"))?; + + let asset = app + .person + .headshot + .to_owned() + .unwrap_or(AssetInner::Assets("fallback-Person.avif".into()).ser()); + + Ok(asset) +} + +pub async fn get_node_thumbnail(_session: &Session, id: NodeID, t: f64) -> Result<Asset> { + let node = DATABASE + .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 + .tracks + .iter() + .enumerate() + .find(|(_i, t)| matches!(t.kind, SourceTrackKind::Video { .. })) + .ok_or(anyhow!("no video track to create a thumbnail of"))?; + let source = media + .tracks + .get(thumb_track_index) + .ok_or(anyhow!("no source"))?; + let thumb_track_source = source.source.clone(); + + if t < 0. || t > media.duration { + Err(anyhow!("thumbnail instant not within media duration"))? + } + + let step = 8.; + let t = (t / step).floor() * step; + + let asset = match thumb_track_source { + TrackSource::Local(a) => { + let AssetInner::LocalTrack(LocalTrack { path, .. }) = AssetInner::deser(&a.0)? else { + return Err(anyhow!("track set to wrong asset type").into()); + }; + // the track selected might be different from thumb_track + jellytranscoder::thumbnail::create_thumbnail(&path, t).await? + } + TrackSource::Remote(_) => { + // // TODO in the new system this is preferrably a property of node ext for regular fed + // let session = fed + // .get_session( + // thumb_track + // .federated + // .last() + // .ok_or(anyhow!("federation broken"))?, + // ) + // .await?; + + // async_cache_file("fed-thumb", (id.0, t as i64), |out| { + // session.node_thumbnail(out, id.0.into(), 2048, t) + // }) + // .await? + todo!() + } + }; + + Ok(AssetInner::Cache(asset).ser()) +} diff --git a/logic/src/home.rs b/logic/src/home.rs index ad3fee5..1957a94 100644 --- a/logic/src/home.rs +++ b/logic/src/home.rs @@ -4,7 +4,7 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{node::DatabaseNodeUserDataExt, session::Session}; +use crate::{DATABASE, node::DatabaseNodeUserDataExt, session::Session}; use anyhow::{Context, Result}; use jellycommon::{ NodeID, NodeKind, Rating, Visibility, @@ -12,16 +12,15 @@ use jellycommon::{ chrono::{Datelike, Utc}, user::WatchedState, }; -use jellydb::Database; -pub fn home(db: &Database, session: &Session) -> Result<ApiHomeResponse> { - let mut items = db.list_nodes_with_udata(&session.user.name)?; +pub fn home(session: &Session) -> Result<ApiHomeResponse> { + let mut items = DATABASE.list_nodes_with_udata(&session.user.name)?; - let mut toplevel = db + let mut toplevel = DATABASE .get_node_children(NodeID::from_slug("library")) .context("root node missing")? .into_iter() - .map(|n| db.get_node_with_userdata(n, &session)) + .map(|n| DATABASE.get_node_with_userdata(n, &session)) .collect::<anyhow::Result<Vec<_>>>()?; toplevel.sort_by_key(|(n, _)| n.index.unwrap_or(usize::MAX)); diff --git a/logic/src/items.rs b/logic/src/items.rs index 99fb767..eddfb03 100644 --- a/logic/src/items.rs +++ b/logic/src/items.rs @@ -4,21 +4,19 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{filter_sort::filter_and_sort_nodes, session::Session}; +use crate::{DATABASE, filter_sort::filter_and_sort_nodes, session::Session}; use anyhow::Result; use jellycommon::{ Visibility, api::{ApiItemsResponse, NodeFilterSort, SortOrder, SortProperty}, }; -use jellydb::Database; pub fn all_items( - db: &Database, session: &Session, page: Option<usize>, filter: NodeFilterSort, ) -> Result<ApiItemsResponse> { - let mut items = db.list_nodes_with_udata(session.user.name.as_str())?; + let mut items = DATABASE.list_nodes_with_udata(session.user.name.as_str())?; items.retain(|(n, _)| matches!(n.visibility, Visibility::Visible)); diff --git a/logic/src/lib.rs b/logic/src/lib.rs index 004e008..9988ed2 100644 --- a/logic/src/lib.rs +++ b/logic/src/lib.rs @@ -6,6 +6,7 @@ #![feature(duration_constructors, let_chains)] pub mod admin; +pub mod assets; pub mod filter_sort; pub mod home; pub mod items; @@ -14,10 +15,13 @@ pub mod node; pub mod search; pub mod session; pub mod stats; +pub mod account; -pub use jellydb::Database; - +use anyhow::Context; +use anyhow::Result; +use jellydb::Database; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; use std::sync::LazyLock; use std::sync::Mutex; @@ -28,6 +32,7 @@ pub struct Config { session_key: Option<String>, admin_username:Option<String>, admin_password:Option<String>, + database_path: PathBuf, } pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None); @@ -47,3 +52,11 @@ static DATABASE: LazyLock<Database> = LazyLock::new(|| { .take() .expect("database not preloaded. logic error") }); + +pub fn init_database() -> Result<()> { + let database = Database::open(&CONF.database_path) + .context("opening database") + .unwrap(); + *DATABASE_PRELOAD.lock().unwrap() = Some(database); + Ok(()) +} diff --git a/logic/src/login.rs b/logic/src/login.rs index 72a5903..5e255a0 100644 --- a/logic/src/login.rs +++ b/logic/src/login.rs @@ -3,19 +3,18 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{CONF, session::create}; +use crate::{CONF, DATABASE, session::create}; use anyhow::{Result, anyhow}; use argon2::{Argon2, PasswordHasher, password_hash::Salt}; use jellycommon::user::UserPermission; -use jellydb::Database; use log::info; use std::{collections::HashSet, time::Duration}; -pub fn create_admin_account(database: &Database) -> Result<()> { +pub fn create_admin_account() -> Result<()> { if let Some(username) = &CONF.admin_username && let Some(password) = &CONF.admin_password { - database + DATABASE .create_admin_user(username, hash_password(username, password)) .unwrap(); } else { @@ -25,7 +24,6 @@ pub fn create_admin_account(database: &Database) -> Result<()> { } pub fn login_logic( - database: &Database, username: &str, password: &str, expire: Option<i64>, @@ -34,7 +32,7 @@ pub fn login_logic( // hashing the password regardless if the accounts exists to better resist timing attacks let password = hash_password(username, password); - let mut user = database + let mut user = DATABASE .get_user(username)? .ok_or(anyhow!("invalid password"))?; diff --git a/logic/src/node.rs b/logic/src/node.rs index c8ff820..820116f 100644 --- a/logic/src/node.rs +++ b/logic/src/node.rs @@ -3,30 +3,30 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{filter_sort::filter_and_sort_nodes, session::Session}; +use crate::{DATABASE, filter_sort::filter_and_sort_nodes, session::Session}; use anyhow::{Result, anyhow}; use jellycommon::{ Node, NodeID, NodeKind, Visibility, api::{ApiNodeResponse, NodeFilterSort, SortOrder, SortProperty}, - user::NodeUserData, + user::{NodeUserData, WatchedState}, }; use jellydb::Database; use std::{cmp::Reverse, collections::BTreeMap, sync::Arc}; pub fn get_node( - db: &Database, - id: NodeID, session: &Session, + id: NodeID, children: bool, parents: bool, filter: NodeFilterSort, ) -> Result<ApiNodeResponse> { - let (node, udata) = db.get_node_with_userdata(id, &session)?; + let (node, udata) = DATABASE.get_node_with_userdata(id, &session)?; let mut children = if children { - db.get_node_children(id)? + DATABASE + .get_node_children(id)? .into_iter() - .map(|c| db.get_node_with_userdata(c, &session)) + .map(|c| DATABASE.get_node_with_userdata(c, &session)) .collect::<anyhow::Result<Vec<_>>>()? } else { Vec::new() @@ -35,13 +35,13 @@ pub fn get_node( let mut parents = if parents { node.parents .iter() - .map(|pid| db.get_node_with_userdata(*pid, &session)) + .map(|pid| DATABASE.get_node_with_userdata(*pid, &session)) .collect::<anyhow::Result<Vec<_>>>()? } else { Vec::new() }; - let mut similar = get_similar_media(&node, db, &session)?; + let mut similar = get_similar_media(&session, &node)?; similar.retain(|(n, _)| n.visibility >= Visibility::Reduced); children.retain(|(n, _)| n.visibility >= Visibility::Reduced); @@ -65,15 +65,11 @@ pub fn get_node( }) } -pub fn get_similar_media( - node: &Node, - db: &Database, - session: &Session, -) -> Result<Vec<(Arc<Node>, NodeUserData)>> { +pub fn get_similar_media(session: &Session, node: &Node) -> Result<Vec<(Arc<Node>, NodeUserData)>> { let this_id = NodeID::from_slug(&node.slug); let mut ranking = BTreeMap::<NodeID, usize>::new(); for tag in &node.tags { - let nodes = db.get_tag_nodes(tag)?; + let nodes = DATABASE.get_tag_nodes(tag)?; let weight = 1_000_000 / nodes.len(); for n in nodes { if n != this_id { @@ -86,7 +82,7 @@ pub fn get_similar_media( ranking .into_iter() .take(32) - .map(|(pid, _)| db.get_node_with_userdata(pid, session)) + .map(|(pid, _)| DATABASE.get_node_with_userdata(pid, session)) .collect::<anyhow::Result<Vec<_>>>() } @@ -110,3 +106,60 @@ impl DatabaseNodeUserDataExt for Database { )) } } + +pub fn get_nodes_modified_since(_session: &Session, since: u64) -> Result<Vec<NodeID>> { + let mut nodes = DATABASE.get_nodes_modified_since(since)?; + nodes.retain(|id| { + DATABASE.get_node(*id).is_ok_and(|n| { + n.as_ref() + .is_some_and(|n| n.visibility >= Visibility::Reduced) + }) + }); + Ok(nodes) +} + +pub fn get_node_by_eid(_session: &Session, platform: &str, eid: &str) -> Result<Option<NodeID>> { + DATABASE.get_node_external_id(platform, eid) +} +pub fn node_id_to_slug(_session: &Session, id: NodeID) -> Result<String> { + Ok(DATABASE + .get_node(id)? + .ok_or(anyhow!("node does not exist"))? + .slug + .to_owned()) +} + +pub fn update_node_userdata_watched( + session: &Session, + node: NodeID, + state: WatchedState, +) -> Result<()> { + // TODO perm + DATABASE.update_node_udata(node, &session.user.name, |udata| { + udata.watched = state; + Ok(()) + }) +} +pub fn update_node_userdata_watched_progress( + session: &Session, + node: NodeID, + time: f64, +) -> Result<()> { + // TODO perm + DATABASE.update_node_udata(node, &session.user.name, |udata| { + udata.watched = match udata.watched { + WatchedState::None | WatchedState::Pending | WatchedState::Progress(_) => { + WatchedState::Progress(time) + } + WatchedState::Watched => WatchedState::Watched, + }; + Ok(()) + }) +} +pub fn update_node_userdata_rating(session: &Session, node: NodeID, rating: i32) -> Result<()> { + // TODO perm + DATABASE.update_node_udata(node, &session.user.name, |udata| { + udata.rating = rating; + Ok(()) + }) +} diff --git a/logic/src/search.rs b/logic/src/search.rs index 68975f1..304676b 100644 --- a/logic/src/search.rs +++ b/logic/src/search.rs @@ -3,23 +3,17 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{node::DatabaseNodeUserDataExt, session::Session}; +use crate::{DATABASE, node::DatabaseNodeUserDataExt, session::Session}; use anyhow::Result; use jellycommon::{Visibility, api::ApiSearchResponse}; -use jellydb::Database; use std::time::Instant; -pub fn search( - db: &Database, - session: &Session, - query: &str, - page: Option<usize>, -) -> Result<ApiSearchResponse> { +pub fn search(session: &Session, query: &str, page: Option<usize>) -> Result<ApiSearchResponse> { let timing = Instant::now(); - let (count, ids) = db.search(query, 32, page.unwrap_or_default() * 32)?; + let (count, ids) = DATABASE.search(query, 32, page.unwrap_or_default() * 32)?; let mut results = ids .into_iter() - .map(|id| db.get_node_with_userdata(id, &session)) + .map(|id| DATABASE.get_node_with_userdata(id, &session)) .collect::<Result<Vec<_>, anyhow::Error>>()?; results.retain(|(n, _)| n.visibility >= Visibility::Reduced); let duration = timing.elapsed(); diff --git a/logic/src/session.rs b/logic/src/session.rs index 72a1089..615694c 100644 --- a/logic/src/session.rs +++ b/logic/src/session.rs @@ -3,7 +3,7 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::CONF; +use crate::{CONF, DATABASE}; use aes_gcm_siv::{ KeyInit, aead::{Aead, generic_array::GenericArray}, @@ -85,10 +85,27 @@ pub fn validate(token: &str) -> anyhow::Result<String> { Ok(session_data.username) } +pub fn token_to_session(token: &str) -> anyhow::Result<Session> { + let username = validate(token)?; + let user = DATABASE + .get_user(&username)? + .ok_or(anyhow!("user does not exist"))?; + Ok(Session { user }) +} +pub fn bypass_auth_session() -> anyhow::Result<Session> { + let user = DATABASE + .get_user(&CONF.admin_username.as_ref().unwrap())? + .ok_or(anyhow!("user does not exist"))?; + Ok(Session { user }) +} + #[cfg(test)] fn load_test_config() { + use std::path::PathBuf; + use crate::{CONF_PRELOAD, Config}; *CONF_PRELOAD.lock().unwrap() = Some(Config { + database_path: PathBuf::default(), login_expire: 10, session_key: None, admin_password: None, diff --git a/logic/src/stats.rs b/logic/src/stats.rs index 2e962e2..c7464f9 100644 --- a/logic/src/stats.rs +++ b/logic/src/stats.rs @@ -4,17 +4,16 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::session::Session; +use crate::{DATABASE, session::Session}; use anyhow::Result; use jellycommon::{ Node, NodeKind, Visibility, api::{ApiStatsResponse, StatsBin}, }; -use jellydb::Database; use std::collections::BTreeMap; -pub fn stats(db: &Database, session: &Session) -> Result<ApiStatsResponse> { - let mut items = db.list_nodes_with_udata(session.user.name.as_str())?; +pub fn stats(session: &Session) -> Result<ApiStatsResponse> { + let mut items = DATABASE.list_nodes_with_udata(session.user.name.as_str())?; items.retain(|(n, _)| n.visibility >= Visibility::Reduced); trait BinExt { |