aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-02-10 01:11:36 +0100
committermetamuffin <metamuffin@disroot.org>2026-02-10 01:11:36 +0100
commitf035474090d3c82f50c3860cbafd6f60b8af36e8 (patch)
treead05b9df5770bee3c1f020870911f7744977422d
parent7754a042ed80c7d8e2391925a8a6ae87a7610c8e (diff)
downloadjellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar
jellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar.bz2
jellything-f035474090d3c82f50c3860cbafd6f60b8af36e8.tar.zst
fix index key ser; query debug print
-rw-r--r--common/object/src/inspect.rs6
-rw-r--r--common/object/src/registry.rs4
-rw-r--r--database/src/kv/index.rs1
-rw-r--r--database/src/kv/index_key.rs35
-rw-r--r--database/src/kv/mod.rs39
-rw-r--r--database/src/lib.rs7
-rw-r--r--database/src/query_ser.rs94
-rw-r--r--database/src/test_shared.rs3
-rw-r--r--server/Cargo.toml2
-rw-r--r--server/src/compat/youtube.rs36
-rw-r--r--server/src/main.rs44
-rw-r--r--server/src/routes.rs5
-rw-r--r--server/src/ui/home.rs21
-rw-r--r--server/src/ui/mod.rs2
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;