/* 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 */ use crate::{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, }; use jellydb::Database; use std::{cmp::Reverse, collections::BTreeMap, sync::Arc}; pub fn get_node( db: &Database, id: NodeID, session: &Session, children: bool, parents: bool, filter: NodeFilterSort, ) -> Result { let (node, udata) = db.get_node_with_userdata(id, &session)?; let mut children = if children { db.get_node_children(id)? .into_iter() .map(|c| db.get_node_with_userdata(c, &session)) .collect::>>()? } else { Vec::new() }; let mut parents = if parents { node.parents .iter() .map(|pid| db.get_node_with_userdata(*pid, &session)) .collect::>>()? } else { Vec::new() }; let mut similar = get_similar_media(&node, db, &session)?; similar.retain(|(n, _)| n.visibility >= Visibility::Reduced); children.retain(|(n, _)| n.visibility >= Visibility::Reduced); parents.retain(|(n, _)| n.visibility >= Visibility::Reduced); filter_and_sort_nodes( &filter, match node.kind { NodeKind::Channel => (SortProperty::ReleaseDate, SortOrder::Descending), NodeKind::Season | NodeKind::Show => (SortProperty::Index, SortOrder::Ascending), _ => (SortProperty::Title, SortOrder::Ascending), }, &mut children, ); Ok(ApiNodeResponse { children, parents, node, userdata: udata, }) } pub fn get_similar_media( node: &Node, db: &Database, session: &Session, ) -> Result, NodeUserData)>> { let this_id = NodeID::from_slug(&node.slug); let mut ranking = BTreeMap::::new(); for tag in &node.tags { let nodes = db.get_tag_nodes(tag)?; let weight = 1_000_000 / nodes.len(); for n in nodes { if n != this_id { *ranking.entry(n).or_default() += weight; } } } let mut ranking = ranking.into_iter().collect::>(); ranking.sort_by_key(|(_, k)| Reverse(*k)); ranking .into_iter() .take(32) .map(|(pid, _)| db.get_node_with_userdata(pid, session)) .collect::>>() } pub trait DatabaseNodeUserDataExt { fn get_node_with_userdata( &self, id: NodeID, session: &Session, ) -> Result<(Arc, NodeUserData)>; } impl DatabaseNodeUserDataExt for Database { fn get_node_with_userdata( &self, id: NodeID, session: &Session, ) -> Result<(Arc, NodeUserData)> { Ok(( self.get_node(id)?.ok_or(anyhow!("node does not exist"))?, self.get_node_udata(id, &session.user.name)? .unwrap_or_default(), )) } }