/* 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) 2026 metamuffin */ use super::error::MyResult; use crate::{request_info::RequestInfo, ui_responder::UiResponse}; use anyhow::Result; use jellycommon::{ jellyobject::{Object, ObjectBuffer, ObjectBufferBuilder, Path}, *, }; use jellydb::{Filter, MultiBehaviour, Query, Sort, SortOrder, Transaction, ValueSort}; use rocket::get; #[get("/n/")] pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult { ri.require_user()?; let mut page_out = ObjectBuffer::empty(); ri.state.database.transaction(&mut |txn| { if let Some(row) = txn.query_single(Query { filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()), sort: Sort::None, })? { let n = txn.get(row)?.unwrap(); let nku = Object::EMPTY.insert(NKU_NODE, n.as_object()); let nku = nku.as_object(); let mut page = ObjectBufferBuilder::default(); let title = nku .get(NKU_NODE) .unwrap_or_default() .get(NO_TITLE) .unwrap_or_default(); page.push(VIEW_TITLE, title); page.push(VIEW_NODE_PAGE, nku); c_children(&mut page, txn, row, &nku)?; c_credits(&mut page, txn, &nku)?; c_credited(&mut page, txn, row)?; page_out = page.finish(); } Ok(()) })?; Ok(ri.respond_ui(page_out)) } fn c_children( page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64, nku: &Object, ) -> Result<()> { let kind = nku .get(NKU_NODE) .unwrap_or_default() .get(NO_KIND) .unwrap_or(KIND_COLLECTION); let (order, path) = match kind { KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])), KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])), _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])), }; let children_rows = txn .query(Query { sort: Sort::Value(ValueSort { multi: MultiBehaviour::First, offset: None, order, path, }), filter: Filter::All(vec![ Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), Filter::Match(Path(vec![NO_PARENT.0]), row.into()), ]), })? .collect::>>()?; if children_rows.is_empty() { return Ok(()); } let mut list = ObjectBufferBuilder::default(); list.push( NODELIST_DISPLAYSTYLE, match kind { KIND_SEASON | KIND_SHOW => NLSTYLE_LIST, _ => NLSTYLE_GRID, }, ); for (row, _) in children_rows { list.push( NODELIST_ITEM, Object::EMPTY .insert(NKU_NODE, txn.get(row)?.unwrap().as_object()) .as_object(), ); } page.push(VIEW_NODE_LIST, list.finish().as_object()); Ok(()) } fn c_credits( page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, nku: &Object, ) -> Result<()> { let mut list = ObjectBufferBuilder::default(); list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE); list.push(NODELIST_TITLE, "node.credits"); if !nku.get(NKU_NODE).unwrap_or_default().has(NO_CREDIT.0) { return Ok(()); } for cred in nku.get(NKU_NODE).unwrap_or_default().iter(NO_CREDIT) { let mut o = ObjectBuffer::empty(); if let Some(row) = cred.get(CR_NODE) { let node = txn.get(row)?.unwrap(); o = o.as_object().insert(NKU_NODE, node.as_object()); } if let Some(role) = cred.get(CR_ROLE) { o = o.as_object().insert(NKU_ROLE, role) } list.push(NODELIST_ITEM, o.as_object()); } page.push(VIEW_NODE_LIST, list.finish().as_object()); Ok(()) } fn c_credited(page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64) -> Result<()> { let children_rows = txn .query(Query { sort: Sort::Value(ValueSort { multi: MultiBehaviour::First, offset: None, order: SortOrder::Ascending, path: Path(vec![NO_TITLE.0]), }), filter: Filter::All(vec![ Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()), Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()), ]), })? .collect::>>()?; if children_rows.is_empty() { return Ok(()); } let mut list = ObjectBufferBuilder::default(); list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_GRID); list.push(NODELIST_TITLE, "node.credited"); for (row, _) in children_rows { list.push( NODELIST_ITEM, Object::EMPTY .insert(NKU_NODE, txn.get(row)?.unwrap().as_object()) .as_object(), ); } page.push(VIEW_NODE_LIST, list.finish().as_object()); Ok(()) }