diff options
Diffstat (limited to 'base')
-rw-r--r-- | base/src/database.rs | 318 | ||||
-rw-r--r-- | base/src/permission.rs | 11 |
2 files changed, 263 insertions, 66 deletions
diff --git a/base/src/database.rs b/base/src/database.rs index 0195209..0f18097 100644 --- a/base/src/database.rs +++ b/base/src/database.rs @@ -3,44 +3,40 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use anyhow::Context; +use anyhow::{anyhow, bail, Context, Result}; use bincode::{Decode, Encode}; use jellycommon::{ user::{NodeUserData, User}, - Node, + Node, NodeID, }; use log::info; -use redb::{Database, TableDefinition}; -use serde::{Deserialize, Serialize}; +use redb::{ReadableTable, TableDefinition}; use std::{ - borrow::Borrow, fs::create_dir_all, - ops::Deref, path::Path, sync::{Arc, RwLock}, }; use tantivy::{ + collector::{Count, TopDocs}, directory::MmapDirectory, - schema::{Field, Schema, FAST, INDEXED, STORED, STRING, TEXT}, - DateOptions, Index, IndexReader, IndexWriter, ReloadPolicy, + query::QueryParser, + schema::{Field, Schema, Value, FAST, INDEXED, STORED, STRING, TEXT}, + DateOptions, Index, IndexReader, IndexWriter, ReloadPolicy, TantivyDocument, }; -pub use redb; -pub use tantivy; - -pub const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user"); -pub const T_USER_NODE: TableDefinition<(&str, &str), Ser<NodeUserData>> = +const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user"); +const T_USER_NODE: TableDefinition<(&str, [u8; 32]), Ser<NodeUserData>> = TableDefinition::new("user_node"); -pub const T_INVITE: TableDefinition<&str, Ser<()>> = TableDefinition::new("invite"); -pub const T_NODE: TableDefinition<&str, Ser<Node>> = TableDefinition::new("node"); +const T_INVITE: TableDefinition<&str, ()> = TableDefinition::new("invite"); +const T_NODE: TableDefinition<[u8; 32], Ser<Node>> = TableDefinition::new("node"); #[derive(Clone)] -pub struct DataAcid { - pub inner: Arc<redb::Database>, - pub node_index: Arc<NodeIndex>, +pub struct Database { + inner: Arc<redb::Database>, + node_index: Arc<NodeIndex>, } -impl DataAcid { +impl Database { pub fn open(path: &Path) -> Result<Self, anyhow::Error> { create_dir_all(path).context("creating database directory")?; info!("opening kv store..."); @@ -54,7 +50,7 @@ impl DataAcid { { // this creates all tables such that read operations on them do not fail. - let txn = r.begin_write()?; + let txn = r.inner.begin_write()?; drop(txn.open_table(T_INVITE)?); drop(txn.open_table(T_USER)?); drop(txn.open_table(T_USER_NODE)?); @@ -65,12 +61,212 @@ impl DataAcid { info!("ready"); Ok(r) } -} + pub fn get_node_slug(&self, slug: &str) -> Result<Option<Arc<Node>>> { + self.get_node(NodeID::from_slug(slug)) + } + pub fn get_node(&self, id: NodeID) -> Result<Option<Arc<Node>>> { + let txn = self.inner.begin_read()?; + let t_node = txn.open_table(T_NODE)?; + if let Some(node) = t_node.get(id.0)? { + Ok(Some(node.value().0.into())) + } else { + Ok(None) + } + } + pub fn get_node_udata(&self, id: NodeID, username: &str) -> Result<Option<NodeUserData>> { + let txn = self.inner.begin_read()?; + let t_node = txn.open_table(T_USER_NODE)?; + if let Some(node) = t_node.get((username, id.0))? { + Ok(Some(node.value().0.into())) + } else { + Ok(None) + } + } + pub fn get_user(&self, username: &str) -> Result<Option<User>> { + let txn = self.inner.begin_read()?; + let t_user = txn.open_table(T_USER)?; + if let Some(user) = t_user.get(username)? { + Ok(Some(user.value().0)) + } else { + Ok(None) + } + } + pub fn update_user( + &self, + username: &str, + update: impl FnOnce(&mut User) -> Result<()>, + ) -> Result<()> { + let txn = self.inner.begin_write()?; + let mut users = txn.open_table(T_USER)?; + let mut user = users + .get(username)? + .ok_or(anyhow!("user does not exist"))? + .value() + .0; + update(&mut user)?; + users.insert(&username, Ser(user))?; + drop(users); + txn.commit()?; + + Ok(()) + } + pub fn update_node_udata( + &self, + node: NodeID, + username: &str, + update: impl FnOnce(&mut NodeUserData) -> Result<()>, + ) -> Result<()> { + let txn = self.inner.begin_write()?; + let mut user_nodes = txn.open_table(T_USER_NODE)?; + + let mut udata = user_nodes + .get((username, node.0))? + .map(|x| x.value().0) + .unwrap_or_default(); -impl Deref for DataAcid { - type Target = Database; - fn deref(&self) -> &Self::Target { - &self.inner + update(&mut udata)?; + + user_nodes.insert((username, node.0), Ser(udata))?; + drop(user_nodes); + txn.commit()?; + Ok(()) + } + pub fn delete_user(&self, username: &str) -> Result<bool> { + let txn = self.inner.begin_write()?; + let mut table = txn.open_table(T_USER)?; + let r = table.remove(username)?.is_some(); + drop(table); + txn.commit()?; + Ok(r) + } + pub fn list_users(&self) -> Result<Vec<User>> { + let txn = self.inner.begin_read()?; + let table = txn.open_table(T_USER)?; + let i = table + .iter()? + .map(|a| { + let (_, y) = a.unwrap(); // TODO + y.value().0 + }) + .collect::<Vec<_>>(); + drop(table); + Ok(i) + } + pub fn list_invites(&self) -> Result<Vec<String>> { + let txn = self.inner.begin_read()?; + let table = txn.open_table(T_INVITE)?; + let i = table + .iter()? + .map(|a| { + let (x, _) = a.unwrap(); + x.value().to_owned() + }) + .collect::<Vec<_>>(); + drop(table); + Ok(i) + } + pub fn create_invite(&self, inv: &str) -> Result<()> { + let txn = self.inner.begin_write()?; + let mut table = txn.open_table(T_INVITE)?; + table.insert(inv, ())?; + drop(table); + txn.commit()?; + Ok(()) + } + pub fn delete_invite(&self, inv: &str) -> Result<bool> { + let txn = self.inner.begin_write()?; + let mut table = txn.open_table(T_INVITE)?; + let r = table.remove(inv)?.is_some(); + drop(table); + txn.commit()?; + Ok(r) + } + pub fn register_user(&self, invite: &str, username: &str, user: User) -> Result<()> { + let txn = self.inner.begin_write()?; + let mut invites = txn.open_table(T_INVITE)?; + let mut users = txn.open_table(T_USER)?; + + if invites.remove(invite)?.is_none() { + bail!("invitation invalid"); + } + let prev_user = users.insert(username, Ser(user))?.map(|x| x.value().0); + if prev_user.is_some() { + bail!("username taken"); + } + + drop(users); + drop(invites); + txn.commit()?; + Ok(()) + } + pub fn list_nodes_with_udata(&self, username: &str) -> Result<Vec<(Node, NodeUserData)>> { + let txn = self.inner.begin_read()?; + let nodes = txn.open_table(T_NODE)?; + let node_users = txn.open_table(T_USER_NODE)?; + let i = nodes + .iter()? + .map(|a| { + let (x, y) = a.unwrap(); + let (x, y) = (x.value().to_owned(), y.value().0); + let z = node_users + .get(&(username, x)) + .unwrap() + .map(|z| z.value().0) + .unwrap_or_default(); + (y, z) + }) + .collect::<Vec<_>>(); + drop(nodes); + Ok(i) + } + pub fn search(&self, query: &str, page: usize) -> Result<(usize, Vec<NodeID>)> { + let query = QueryParser::for_index( + &self.node_index.index, + vec![self.node_index.title, self.node_index.description], + ) + .parse_query(query) + .context("parsing query")?; + + let searcher = self.node_index.reader.searcher(); + let sres = searcher.search(&query, &TopDocs::with_limit(32).and_offset(page * 32))?; + let scount = searcher.search(&query, &Count)?; + + let mut results = Vec::new(); + for (_, daddr) in sres { + let doc: TantivyDocument = searcher.doc(daddr)?; + let id = doc + .get_first(self.node_index.id) + .unwrap() + .as_bytes() + .unwrap(); + let id = NodeID(id.try_into().unwrap()); + results.push(id); + } + Ok((scount, results)) + } + pub fn create_admin_user(&self, username: &str, password_hash: Vec<u8>) -> Result<()> { + let txn = self.inner.begin_write().unwrap(); + let mut users = txn.open_table(T_USER).unwrap(); + + let admin = users.get(username).unwrap().map(|x| x.value().0); + users + .insert( + username, + Ser(User { + admin: true, + name: username.to_owned(), + password: password_hash, + ..admin.unwrap_or_else(|| User { + display_name: "Admin".to_string(), + ..Default::default() + }) + }), + ) + .unwrap(); + + drop(users); + txn.commit().unwrap(); + Ok(()) } } @@ -126,42 +322,42 @@ impl NodeIndex { } } -pub trait TableExt<Key, KeyRef, Value> { - fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>; - fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()>; - fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>; -} -impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt<Key, KeyRef, Value> - for redb::TableDefinition<'a, Key, Ser<Value>> -where - Key: Borrow<<Key as redb::Value>::SelfType<'b>> + redb::Key, - Value: Encode + Decode + std::fmt::Debug + Serialize + for<'x> Deserialize<'x>, - KeyRef: Borrow<<Key as redb::Value>::SelfType<'c>>, -{ - fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> { - let txn = db.inner.begin_read()?; - let table = txn.open_table(self)?; - let user = table.get(key)?.map(|v| v.value().0); - drop(table); - Ok(user) - } - fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()> { - let txn = db.inner.begin_write()?; - let mut table = txn.open_table(self)?; - table.insert(key, Ser(value))?; - drop(table); - txn.commit()?; - Ok(()) - } - fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> { - let txn = db.inner.begin_write()?; - let mut table = txn.open_table(self)?; - let prev = table.remove(key)?.map(|v| v.value().0); - drop(table); - txn.commit()?; - Ok(prev) - } -} +// pub trait TableExt<Key, KeyRef, Value> { +// fn get(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>>; +// fn insert(self, db: &Database, key: KeyRef, value: Value) -> anyhow::Result<()>; +// fn remove(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>>; +// } +// impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt<Key, KeyRef, Value> +// for redb::TableDefinition<'a, Key, Ser<Value>> +// where +// Key: Borrow<<Key as redb::Value>::SelfType<'b>> + redb::Key, +// Value: Encode + Decode + std::fmt::Debug + Serialize + for<'x> Deserialize<'x>, +// KeyRef: Borrow<<Key as redb::Value>::SelfType<'c>>, +// { +// fn get(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>> { +// let txn = db.inner.begin_read()?; +// let table = txn.open_table(self)?; +// let user = table.get(key)?.map(|v| v.value().0); +// drop(table); +// Ok(user) +// } +// fn insert(self, db: &Database, key: KeyRef, value: Value) -> anyhow::Result<()> { +// let txn = db.inner.begin_write()?; +// let mut table = txn.open_table(self)?; +// table.insert(key, Ser(value))?; +// drop(table); +// txn.commit()?; +// Ok(()) +// } +// fn remove(self, db: &Database, key: KeyRef) -> anyhow::Result<Option<Value>> { +// let txn = db.inner.begin_write()?; +// let mut table = txn.open_table(self)?; +// let prev = table.remove(key)?.map(|v| v.value().0); +// drop(table); +// txn.commit()?; +// Ok(prev) +// } +// } // pub trait TableIterExt< // 'a, diff --git a/base/src/permission.rs b/base/src/permission.rs index 9dc8ffe..cec3833 100644 --- a/base/src/permission.rs +++ b/base/src/permission.rs @@ -54,11 +54,12 @@ fn check_node_permission(perms: &PermissionSet, node: &Node) -> bool { if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(node.id.clone().unwrap())) { v } else { - for com in node.parents.clone().into_iter() { - if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(com.to_owned())) { - return v; - } - } + // TODO + // for com in node.parents.clone().into_iter() { + // if let Some(v) = perms.check_explicit(&UserPermission::AccessNode(com.to_owned())) { + // return v; + // } + // } true } } |