diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-10 01:11:36 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-10 01:11:36 +0100 |
| commit | f035474090d3c82f50c3860cbafd6f60b8af36e8 (patch) | |
| tree | ad05b9df5770bee3c1f020870911f7744977422d | |
| parent | 7754a042ed80c7d8e2391925a8a6ae87a7610c8e (diff) | |
| download | jellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar jellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar.bz2 jellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar.zst | |
fix index key ser; query debug print
| -rw-r--r-- | common/object/src/inspect.rs | 6 | ||||
| -rw-r--r-- | common/object/src/registry.rs | 4 | ||||
| -rw-r--r-- | database/src/kv/index.rs | 1 | ||||
| -rw-r--r-- | database/src/kv/index_key.rs | 35 | ||||
| -rw-r--r-- | database/src/kv/mod.rs | 39 | ||||
| -rw-r--r-- | database/src/lib.rs | 7 | ||||
| -rw-r--r-- | database/src/query_ser.rs | 94 | ||||
| -rw-r--r-- | database/src/test_shared.rs | 3 | ||||
| -rw-r--r-- | server/Cargo.toml | 2 | ||||
| -rw-r--r-- | server/src/compat/youtube.rs | 36 | ||||
| -rw-r--r-- | server/src/main.rs | 44 | ||||
| -rw-r--r-- | server/src/routes.rs | 5 | ||||
| -rw-r--r-- | server/src/ui/home.rs | 21 | ||||
| -rw-r--r-- | server/src/ui/mod.rs | 2 |
14 files changed, 260 insertions, 39 deletions
diff --git a/common/object/src/inspect.rs b/common/object/src/inspect.rs index 21648bd..bc217f0 100644 --- a/common/object/src/inspect.rs +++ b/common/object/src/inspect.rs @@ -24,10 +24,14 @@ impl Debug for Inspector<'_, Object<'_>> { }; match ty { x if x == STR => s.field(info.name, &self.1.get_typed::<&str>(i).unwrap()), + x if x == BINARY => s.field(info.name, &self.1.get_typed::<&[u8]>(i).unwrap()), x if x == OBJECT => s.field(info.name, &self.1.get_typed::<Object>(i).unwrap()), x if x == U32 => s.field(info.name, &self.1.get_typed::<u32>(i).unwrap()), x if x == U64 => s.field(info.name, &self.1.get_typed::<u64>(i).unwrap()), - _ => &mut s, + _ => { + nonexhaustive = true; + &mut s + } }; } if nonexhaustive { diff --git a/common/object/src/registry.rs b/common/object/src/registry.rs index d9da2fb..85d3ff2 100644 --- a/common/object/src/registry.rs +++ b/common/object/src/registry.rs @@ -13,11 +13,12 @@ pub mod types { pub const OBJECT: TypeId = TypeId::of::<Object>(); pub const STR: TypeId = TypeId::of::<&str>(); + pub const BINARY: TypeId = TypeId::of::<&[u8]>(); pub const U32: TypeId = TypeId::of::<u32>(); pub const U64: TypeId = TypeId::of::<u64>(); } -#[derive(Default)] +#[derive(Default, Clone)] pub struct Registry { tags: BTreeMap<Tag, TagInfo>, } @@ -42,6 +43,7 @@ impl Registry { } } +#[derive(Clone)] pub struct TagInfo { pub name: &'static str, pub r#type: Option<TypeId>, diff --git a/database/src/kv/index.rs b/database/src/kv/index.rs index 81a4f55..23b8349 100644 --- a/database/src/kv/index.rs +++ b/database/src/kv/index.rs @@ -63,7 +63,6 @@ pub fn iter_index<'a>( prefix: Vec<u8>, sort: &Sort, ) -> Result<Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>> + 'a>> { - eprintln!("{prefix:?}"); Ok(match sort { Sort::None => Box::new( PrefixIterator { diff --git a/database/src/kv/index_key.rs b/database/src/kv/index_key.rs index a4b2f01..adfc3e8 100644 --- a/database/src/kv/index_key.rs +++ b/database/src/kv/index_key.rs @@ -12,7 +12,7 @@ use crate::{ }; use jellyobject::{Path, Tag}; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct IndexKey(pub Binning, pub SortKey); #[derive(Debug, Hash, PartialEq, Eq)] @@ -45,7 +45,7 @@ impl IndexKey { b = &b[4..]; // remove subtree prefix let binning = Binning::read(&mut b); let sort = SortKey::read(&mut b); - assert!(b.is_empty()); + assert!(b.is_empty(), "index key deser left-over bytes"); Self(binning, sort) } } @@ -85,6 +85,7 @@ impl Binning { impl SortKey { fn read(b: &mut &[u8]) -> Self { let ty = b[0]; + *b = &b[1..]; match ty { 0 => SortKey::None, 1 => SortKey::Count, @@ -108,6 +109,7 @@ impl SortKey { SortKey::None => out.push(0), SortKey::Count => out.push(1), SortKey::Value(path, multi_behaviour) => { + out.push(2); write_path(path, out); out.push(match multi_behaviour { MultiBehaviour::First => 0, @@ -118,6 +120,7 @@ impl SortKey { }); } SortKey::Text(path) => { + out.push(3); write_path(path, out); } } @@ -141,3 +144,31 @@ fn read_path(b: &mut &[u8]) -> Path { } Path(o) } + +#[cfg(test)] +mod test { + use jellyobject::{Path, Tag}; + + use crate::kv::{ + binning::{Binning, BinningComponent}, + index_key::{IndexKey, SortKey}, + }; + + #[test] + fn serialize() { + let t = |k: IndexKey| { + let b = k.to_bytes(); + let k2 = IndexKey::from_bytes(&b); + assert_eq!(k, k2); + }; + + t(IndexKey( + Binning(vec![BinningComponent::Match(Path(vec![Tag(1001)]))]), + SortKey::None, + )); + t(IndexKey( + Binning(vec![BinningComponent::Has(Path(vec![Tag(123), Tag(456)]))]), + SortKey::Text(Path(vec![Tag(789)])), + )); + } +} diff --git a/database/src/kv/mod.rs b/database/src/kv/mod.rs index 8d42ec7..903fc46 100644 --- a/database/src/kv/mod.rs +++ b/database/src/kv/mod.rs @@ -15,7 +15,7 @@ pub mod sort; use std::borrow::Cow; use crate::{ - Database, Query, RowNum, Transaction, + DEBUG_TAGREG, Database, Query, RowNum, Transaction, kv::{ helpers::{read_counter, write_counter}, index::{iter_index, read_count_index, update_index}, @@ -25,7 +25,8 @@ use crate::{ }, }; use anyhow::{Result, anyhow}; -use jellyobject::ObjectBuffer; +use jellyobject::{ObjectBuffer, inspect::Inspector}; +use log::{debug, info}; pub type SubtreeNum = u32; @@ -44,6 +45,9 @@ impl<T: jellykv::Store> Database for T { impl Transaction for &mut dyn jellykv::Transaction { fn insert(&mut self, entry: ObjectBuffer) -> Result<RowNum> { + if let Some(tr) = DEBUG_TAGREG.get() { + debug!("insert {:?}", Inspector(tr, entry.as_object())) + } let mut id_counter = read_counter(*self, &T_ROW_COUNTER.to_be_bytes(), 0)?; let row = id_counter; id_counter += 1; @@ -97,6 +101,7 @@ impl Transaction for &mut dyn jellykv::Transaction { &'a mut self, query: Query, ) -> Result<Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>> + 'a>> { + debug!("query: {}", query.show_debug()); let mut prefixes = Vec::new(); for (binning, mut prefix) in query.filter.get_bins() { let ik = IndexKey(binning, query.sort.key()); @@ -151,6 +156,7 @@ fn alloc_subtree(txn: &mut dyn jellykv::Transaction) -> Result<SubtreeNum> { } fn create_index(txn: &mut dyn jellykv::Transaction, is: SubtreeNum, ik: &IndexKey) -> Result<()> { + info!("creating new index {is}: {ik:?}"); let rowkeys = PrefixIterator { inner: txn.iter(&T_ROWS.to_be_bytes(), false)?, prefix: Cow::Borrowed(&T_ROWS.to_be_bytes()), @@ -246,7 +252,7 @@ mod test { } #[test] - pub fn query_match() -> Result<()> { + pub fn query_match_int() -> Result<()> { let db = jellykv::memory::new(); let mut rows = [0, 0, 0]; @@ -271,4 +277,31 @@ mod test { assert_eq!(result, Some(rows[0])); Ok(()) } + + #[test] + pub fn query_match_str() -> Result<()> { + let db = jellykv::memory::new(); + + let mut rows = [0, 0, 0]; + let mut result = None; + + db.transaction(&mut |txn| { + rows = [ + txn.insert(new_bob())?, + txn.insert(new_alice())?, + txn.insert(new_charlie())?, + ]; + Ok(()) + })?; + db.transaction(&mut |txn| { + result = txn.query_single(Query { + filter: Filter::Match(Path(vec![NAME.0]), "Alice".as_bytes().to_vec()), + sort: Sort::None, + })?; + Ok(()) + })?; + + assert_eq!(result, Some(rows[1])); + Ok(()) + } } diff --git a/database/src/lib.rs b/database/src/lib.rs index c4b2d47..c09340b 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -4,11 +4,16 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ pub mod kv; +pub mod query_ser; #[cfg(test)] pub mod test_shared; +use std::sync::OnceLock; + use anyhow::Result; -use jellyobject::{ObjectBuffer, Path}; +use jellyobject::{ObjectBuffer, Path, Registry}; + +pub static DEBUG_TAGREG: OnceLock<Registry> = OnceLock::new(); pub type RowNum = u64; pub type RowIter = Box<dyn Iterator<Item = Result<(RowNum, Vec<u8>)>>>; diff --git a/database/src/query_ser.rs b/database/src/query_ser.rs new file mode 100644 index 0000000..7698fdc --- /dev/null +++ b/database/src/query_ser.rs @@ -0,0 +1,94 @@ +/* + 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 <metamuffin.org> +*/ + +use crate::{DEBUG_TAGREG, Filter, Query, Sort}; +use jellyobject::{Path, Registry, Tag}; +use std::any::TypeId; + +impl Query { + pub fn show_debug(&self) -> String { + if let Some(r) = DEBUG_TAGREG.get() { + self.show(r) + } else { + "[debug tag registry disabled]".to_string() + } + } + pub fn show(&self, reg: &Registry) -> String { + let mut o = String::new(); + if !matches!(self.filter, Filter::True) { + o += &format!("FILTER {}", self.filter.show(reg)) + } + if !matches!(self.sort, Sort::None) { + o += &format!("SORT {}", self.sort.show(reg)) + } + o + } +} +impl Filter { + pub fn show(&self, reg: &Registry) -> String { + match self { + Filter::True => "TRUE".to_string(), + Filter::All(filters) => format!( + "({})", + filters + .iter() + .map(|f| f.show(reg)) + .collect::<Vec<_>>() + .join(" AND ") + ), + Filter::Any(filters) => format!( + "({})", + filters + .iter() + .map(|f| f.show(reg)) + .collect::<Vec<_>>() + .join(" OR ") + ), + Filter::Match(path, value) => { + format!( + "{} = {}", + show_path(reg, path), + show_value(reg, *path.0.last().unwrap(), value) + ) + } + Filter::Has(path) => show_path(reg, path), + } + } +} +impl Sort { + pub fn show(&self, reg: &Registry) -> String { + match self { + Sort::None => "NONE".to_string(), + Sort::Value(vs) => { + todo!() + } + Sort::TextSearch(path, value) => { + format!("TEXT SEARCH {} = {value:?}", show_path(reg, path),) + } + } + } +} + +fn show_path(reg: &Registry, path: &Path) -> String { + path.0 + .iter() + .map(|s| reg.name(*s)) + .collect::<Vec<_>>() + .join(".") +} +fn show_value(reg: &Registry, tag: Tag, value: &[u8]) -> String { + if let Some(info) = reg.info(tag) + && let Some(ty) = info.r#type + { + if ty == TypeId::of::<&str>() { + format!("{:?}", str::from_utf8(value).unwrap()) + } else { + format!("{value:?}") + } + } else { + format!("{value:?}") + } +} diff --git a/database/src/test_shared.rs b/database/src/test_shared.rs index 82e9ba4..8216113 100644 --- a/database/src/test_shared.rs +++ b/database/src/test_shared.rs @@ -7,9 +7,12 @@ use jellyobject::{ObjectBuffer, Registry, fields}; use std::sync::LazyLock; +use crate::DEBUG_TAGREG; + pub static TAGREG: LazyLock<Registry> = LazyLock::new(|| { let mut reg = Registry::default(); register_fields(&mut reg); + DEBUG_TAGREG.set(reg.clone()).ok().unwrap(); reg }); fields! { diff --git a/server/Cargo.toml b/server/Cargo.toml index f12b8fe..dcaaba6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,7 +11,7 @@ jellyimport = { path = "../import" } jellycache = { path = "../cache" } jellydb = { path = "../database" } jellyui = { path = "../ui" } -jellykv = { path = "../kv", features = ["rocksdb"] } +jellykv = { path = "../kv", features = ["rocksdb", "memory"] } aes-gcm-siv = "0.11.1" anyhow = { workspace = true } diff --git a/server/src/compat/youtube.rs b/server/src/compat/youtube.rs index e511d9b..2ed0406 100644 --- a/server/src/compat/youtube.rs +++ b/server/src/compat/youtube.rs @@ -4,19 +4,37 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use crate::{request_info::RequestInfo, ui::error::MyResult}; +use anyhow::anyhow; +use jellycommon::{ + IDENT_YOUTUBE_VIDEO, NO_IDENTIFIERS, NO_SLUG, jellyobject::Path, routes::u_node_id, +}; +use jellydb::{Filter, Query, Sort}; use rocket::{get, response::Redirect}; #[get("/watch?<v>")] pub fn r_youtube_watch(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> { - // if v.len() != 11 { - // Err(anyhow!("video id length incorrect"))? - // } - // let Some(id) = get_node_by_eid(&session.0, IdentifierType::YoutubeVideo, v)? else { - // Err(anyhow!("element not found"))? - // }; - // let slug = node_id_to_slug(&session.0, id)?; - // Ok(Redirect::to(u_node_slug_player(&slug))) - todo!() + if v.len() != 11 { + Err(anyhow!("video id length incorrect"))? + } + let mut res = None; + ri.state.database.transaction(&mut |txn| { + if let Some(row) = txn.query_single(Query { + filter: Filter::Match( + Path(vec![NO_IDENTIFIERS.0, IDENT_YOUTUBE_VIDEO.0]), + v.as_bytes().to_vec(), + ), + sort: Sort::None, + })? { + res = txn.get(row)?; + } + Ok(()) + })?; + let node = res.ok_or(anyhow!("video not found"))?; + let slug = node + .as_object() + .get(NO_SLUG) + .ok_or(anyhow!("node has no slug"))?; + Ok(Redirect::found(u_node_id(slug))) } #[get("/channel/<id>")] diff --git a/server/src/main.rs b/server/src/main.rs index db02c29..c481eec 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,10 +7,17 @@ #![allow(clippy::needless_borrows_for_generic_args)] #![recursion_limit = "4096"] -use crate::{auth::token::SessionKey, logger::setup_logger}; +use crate::{ + auth::{hash_password, token::SessionKey}, + logger::setup_logger, +}; use anyhow::{Result, anyhow}; use jellycache::Cache; -use jellydb::Database; +use jellycommon::{ + TAGREG, USER_LOGIN, USER_PASSWORD, + jellyobject::{ObjectBuffer, Path}, +}; +use jellydb::{Database, Filter, Query, Sort}; use log::{error, info}; use routes::build_rocket; use serde::Deserialize; @@ -29,6 +36,7 @@ pub mod ui; #[rocket::main] async fn main() { setup_logger(); + jellydb::DEBUG_TAGREG.set(TAGREG.clone()).ok().unwrap(); let state = match create_state() { Ok(s) => s, @@ -58,6 +66,7 @@ pub struct State { pub struct Config { pub ui: jellyui::Config, pub session_key: String, + pub admin_password: String, pub asset_path: PathBuf, pub database_path: PathBuf, pub cache_path: PathBuf, @@ -73,12 +82,37 @@ pub fn create_state() -> Result<Arc<State>> { let config: Config = serde_yaml_ng::from_str(&read_to_string(config_path)?)?; let cache_storage = jellykv::rocksdb::new(&config.cache_path)?; - let db_storage = jellykv::rocksdb::new(&config.database_path)?; + // let db_storage = jellykv::rocksdb::new(&config.database_path)?; + let db_storage = jellykv::memory::new(); - Ok(Arc::new(State { + let state = Arc::new(State { cache: Cache::new(Box::new(cache_storage), config.max_memory_cache_size), database: Arc::new(db_storage), session_key: SessionKey::parse(&config.session_key)?, config, - })) + }); + + create_admin_user(&state)?; + + Ok(state) +} + +fn create_admin_user(state: &State) -> Result<()> { + state.database.transaction(&mut |txn| { + let admin_row = txn.query_single(Query { + filter: Filter::Match(Path(vec![USER_LOGIN.0]), "admin".as_bytes().to_vec()), + sort: Sort::None, + })?; + if admin_row.is_none() { + info!("Creating new admin user"); + let pwhash = hash_password("admin", &state.config.admin_password); + txn.insert(ObjectBuffer::new(&mut [ + (USER_LOGIN.0, &"admin"), + (USER_PASSWORD.0, &pwhash.as_slice()), + ]))?; + } + + Ok(()) + })?; + Ok(()) } diff --git a/server/src/routes.rs b/server/src/routes.rs index 4744561..adbf6e0 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -15,9 +15,10 @@ use crate::{ account::{r_account_login, r_account_login_post, r_account_logout, r_account_logout_post}, assets::r_image, error::{r_api_catch, r_catch}, + home::r_home, node::r_node, r_favicon, - style::{r_assets_font, r_assets_js, r_assets_js_map, r_assets_css}, + style::{r_assets_css, r_assets_font, r_assets_js, r_assets_js_map}, }, }; use rocket::{ @@ -93,7 +94,7 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> { r_assets_js, r_assets_css, r_favicon, - // r_home, + r_home, // r_index, // r_item_poster, r_node, diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs index 4e9fd28..e9136ec 100644 --- a/server/src/ui/home.rs +++ b/server/src/ui/home.rs @@ -5,18 +5,15 @@ */ use super::error::MyResult; -use rocket::{get, response::content::RawHtml, serde::json::Json, Either}; +use crate::request_info::RequestInfo; +use jellycommon::jellyobject::ObjectBuffer; +use jellyui::render_view; +use rocket::{Either, get, response::content::RawHtml, serde::json::Json}; #[get("/home")] -pub fn r_home(ri: RequestInfo) -> MyResult<Either<RawHtml<String>, Json<ApiHomeResponse>>> { - let r = jellylogic::home::home(&ri.session)?; - - Ok(if matches!(ri.accept, Accept::Json) { - Either::Right(Json(r)) - } else { - Either::Left(RawHtml(render_page( - &HomePage { lang: &ri.lang, r }, - ri.render_info(), - ))) - }) +pub fn r_home(ri: RequestInfo<'_>) -> MyResult<RawHtml<String>> { + Ok(RawHtml(render_view( + ri.render_info(), + ObjectBuffer::new(&mut []).as_object(), + ))) } diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs index 55fad6a..112bb6b 100644 --- a/server/src/ui/mod.rs +++ b/server/src/ui/mod.rs @@ -14,7 +14,7 @@ pub mod account; // pub mod admin; pub mod assets; pub mod error; -// pub mod home; +pub mod home; // pub mod items; pub mod node; // pub mod player; |